PY-21164 PY-8325 Fix false positives for packed collections inside collections in python 3.5

* Move indexed elements resolve from inspection to PySubstitutionChunkReference
* Resolve correctly to packed list/tuple inside list/tuple, to packed dict inside dict
* Update tests
This commit is contained in:
Valentina Kiryushkina
2017-05-15 14:37:55 +03:00
committed by Valentina Kiryushkina
parent f608274755
commit 1dfb4dcdee
20 changed files with 373 additions and 278 deletions
@@ -20,12 +20,14 @@ import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.inspections.PyStringFormatParser;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -34,10 +36,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiteralExpression> implements PsiReferenceEx{
public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiteralExpression> implements PsiReferenceEx {
private final int myPosition;
@NotNull private final PyStringFormatParser.SubstitutionChunk myChunk;
private final boolean myIsPercent;
private final TypeEvalContext myTypeEvalContext;
public PySubstitutionChunkReference(@NotNull final PyStringLiteralExpression element,
@NotNull final PyStringFormatParser.SubstitutionChunk chunk, final int position) {
@@ -45,8 +48,11 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
myChunk = chunk;
myPosition = position;
myIsPercent = chunk instanceof PyStringFormatParser.PercentSubstitutionChunk;
final PsiFile file = element.getContainingFile();
myTypeEvalContext = TypeEvalContext.codeAnalysis(file.getProject(), file);
}
@Nullable
@Override
public HighlightSeverity getUnresolvedHighlightSeverity(@NotNull final TypeEvalContext context) {
@@ -82,7 +88,7 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
if (argumentList == null || argumentList.getArguments().length == 0) {
return null;
}
return myChunk.getMappingKey() != null ? resolveKeywordFormat(argumentList) : resolvePositionalFormat(argumentList);
return myChunk.getMappingKey() != null ? resolveKeywordFormat(argumentList).get() : resolvePositionalFormat(argumentList);
}
@Nullable
@@ -99,7 +105,7 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
firstStarArg = starArg;
}
// TODO: Support multiple *args for Python 3.5+
final Ref<PsiElement> resolvedRef = resolvePositionalStarExpression(starArg, n);
final Ref<PyExpression> resolvedRef = resolvePositionalStarExpression(starArg, n);
if (resolvedRef != null) {
final PsiElement resolved = resolvedRef.get();
if (resolved != null) {
@@ -121,34 +127,252 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
return notSureAboutStarArgs ? firstStarArg : null;
}
@Nullable
private PsiElement resolveKeywordFormat(@NotNull PyArgumentList argumentList) {
final PyKeywordArgument keywordResult = argumentList.getKeywordArgument(myChunk.getMappingKey());
if (keywordResult != null) {
return keywordResult.getValueExpression();
@NotNull
private Ref<PyExpression> resolveKeywordFormat(@NotNull PyArgumentList argumentList) {
final Ref<PyExpression> valueExprRef = getKeyValueFromArguments(argumentList);
final String indexElement = ((PyStringFormatParser.NewStyleSubstitutionChunk)myChunk).getMappingKeyElementIndex();
if (valueExprRef != null && !valueExprRef.isNull() && indexElement != null) {
final PyExpression valueExpr = PyPsiUtils.flattenParens(valueExprRef.get());
assert valueExpr != null;
try {
final Integer index = Integer.valueOf(indexElement);
final Ref<PyExpression> resolvedRef = resolveNumericIndex(valueExpr, index);
if (resolvedRef != null) return resolvedRef;
}
catch (NumberFormatException e) {
final Ref<PyExpression> resolvedRef = resolveStringIndex(valueExpr, indexElement);
if (resolvedRef != null) return resolvedRef;
}
}
else {
final List<PyStarArgument> keywordStarArgs = getStarArguments(argumentList, true);
boolean notSureAboutStarArgs = false;
// valueExprRef is null only if there's no corresponding keyword argument and no star arguments
return valueExprRef == null ? Ref.create() : valueExprRef;
}
@Nullable
private Ref<PyExpression> getKeyValueFromArguments(@NotNull PyArgumentList argumentList) {
final PyKeywordArgument valueFromKeywordArg = argumentList.getKeywordArgument(myChunk.getMappingKey());
final List<PyStarArgument> keywordStarArgs = getStarArguments(argumentList, true);
Ref<PyExpression> valueExprRef = null;
if (valueFromKeywordArg != null) {
valueExprRef = Ref.create(valueFromKeywordArg.getValueExpression());
}
else if (!keywordStarArgs.isEmpty()){
for (PyStarArgument arg : keywordStarArgs) {
final Ref<PsiElement> resolvedRef = resolveKeywordStarExpression(arg);
if (resolvedRef != null) {
final PsiElement resolved = resolvedRef.get();
if (resolved != null) {
return resolved;
}
}
else {
notSureAboutStarArgs = true;
final Ref<PyExpression> resolvedRef = resolveKeywordStarExpression(arg);
if (resolvedRef != null && (valueExprRef == null || valueExprRef.get() == null)) {
valueExprRef = resolvedRef;
}
}
return notSureAboutStarArgs ? Iterables.getFirst(keywordStarArgs, null) : null;
if (valueExprRef == null) {
valueExprRef = Ref.create(Iterables.getFirst(keywordStarArgs, null));
}
}
return valueExprRef;
}
@Nullable
private Ref<PyExpression> resolveStringIndex(@NotNull PyExpression valueExpr, @NotNull String indexElement) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
if (valueExpr instanceof PyCallExpression) {
PyReturnStatement[] returnValues = getFunctionReturnValues((PyCallExpression)valueExpr, resolveContext, myTypeEvalContext);
for (PyReturnStatement value : returnValues) {
PyExpression returnValueExpr = PyPsiUtils.flattenParens(value.getExpression());
if (returnValueExpr instanceof PyDictLiteralExpression) {
Ref<PyExpression> resolvedRef = getElementFromDictLiteral((PyDictLiteralExpression)returnValueExpr, indexElement, resolveContext);
if (resolvedRef != null) return resolvedRef;
}
else if (returnValueExpr instanceof PyCallExpression) {
Ref<PyExpression> resolvedRef = resolveDictCall((PyCallExpression)returnValueExpr, indexElement, true);
if (resolvedRef != null) return resolvedRef;
}
}
}
else if (valueExpr instanceof PyDictLiteralExpression) {
Ref<PyExpression> resolvedRef = getElementFromDictLiteral((PyDictLiteralExpression)valueExpr, indexElement, resolveContext);
if (resolvedRef != null) return resolvedRef;
}
else if (valueExpr instanceof PyReferenceExpression) {
PsiElement element = ((PyReferenceExpression)valueExpr).followAssignmentsChain(resolveContext).getElement();
if (element != valueExpr && element instanceof PyExpression) {
return resolveStringIndex((PyExpression)element, indexElement);
}
}
return null;
}
@Nullable
private Ref<PyExpression> resolveNumericIndex(@NotNull PyExpression valueExpr, @NotNull Integer index) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
if (PyUtil.instanceOf(valueExpr, PyListLiteralExpression.class, PyTupleExpression.class, PyStringLiteralExpression.class)) {
Ref<PyExpression> elementRef = getElementByIndex(valueExpr, index);
if (elementRef != null) return elementRef;
}
else if (valueExpr instanceof PyDictLiteralExpression) {
return getElementFromDictLiteral((PyDictLiteralExpression)valueExpr, index, resolveContext);
}
else if (valueExpr instanceof PyCallExpression) {
PyReturnStatement[] returnValues = getFunctionReturnValues((PyCallExpression)valueExpr, resolveContext, myTypeEvalContext);
boolean allReturnValuesForSure = true;
for (PyReturnStatement value : returnValues) {
PyExpression returnValueExpr = PyPsiUtils.flattenParens(value.getExpression());
if (PyUtil.instanceOf(returnValueExpr, PyListLiteralExpression.class, PyDictLiteralExpression.class,
PyTupleExpression.class, PyCallExpression.class)) {
Ref<PyExpression> resolvedRef = resolveNumericIndex(returnValueExpr, index);
if (resolvedRef != null && !resolvedRef.isNull()) {
return resolvedRef;
}
else if (resolvedRef == null){
allReturnValuesForSure = false;
}
}
}
return allReturnValuesForSure ? Ref.create() : null;
}
else if (valueExpr instanceof PyReferenceExpression) {
PsiElement element = ((PyReferenceExpression)valueExpr).followAssignmentsChain(resolveContext).getElement();
if (element != null && element != valueExpr && element instanceof PyExpression) {
//noinspection ConstantConditions
return resolveNumericIndex(PyPsiUtils.flattenParens((PyExpression)element), index);
}
}
return null;
}
@Nullable
private Ref<PyExpression> getElementFromDictLiteral(@NotNull PyDictLiteralExpression valueExpr,
@NotNull Integer index,
@NotNull PyResolveContext resolveContext) {
boolean allKeysForSure = true;
final PyKeyValueExpression[] elements = valueExpr.getElements();
for (PyKeyValueExpression element : elements) {
final PyNumericLiteralExpression key = PyUtil.as(element.getKey(), PyNumericLiteralExpression.class);
if (key != null && new Long(index).equals(key.getLongValue())) {
return Ref.create(element.getValue());
}
else if (!(element.getKey() instanceof PyLiteralExpression)) {
allKeysForSure = false;
}
}
PyDoubleStarExpression[] starExpressions = PsiTreeUtil.getChildrenOfType(valueExpr, PyDoubleStarExpression.class);
if (starExpressions != null) {
for (PyDoubleStarExpression expression : starExpressions) {
PyExpression underStarExpr = PyPsiUtils.flattenParens(expression.getExpression());
if (underStarExpr != null) {
if (underStarExpr instanceof PyDictLiteralExpression) {
return getElementFromDictLiteral((PyDictLiteralExpression)underStarExpr, index, resolveContext);
}
else if (underStarExpr instanceof PyCallExpression) {
return getElementFromCallExpression((PyCallExpression)underStarExpr, index.toString(), resolveContext, true);
}
}
}
}
return allKeysForSure ? Ref.create() : null;
}
@Nullable
private Ref<PyExpression> getElementFromCallExpression(@NotNull PyCallExpression valueExpr,
@NotNull String key,
@NotNull PyResolveContext resolveContext,
boolean goDeep) {
PyReturnStatement[] returnValues = getFunctionReturnValues(valueExpr, resolveContext, myTypeEvalContext);
boolean allReturnValuesForSure = true;
final PyExpression callee = valueExpr.getCallee();
if (callee != null && "dict".equals(callee.getName())) {
return resolveDictCall(valueExpr, key, goDeep);
}
for (PyReturnStatement value : returnValues) {
PyExpression returnValueExpr = PyPsiUtils.flattenParens(value.getExpression());
if (returnValueExpr instanceof PyDictLiteralExpression) {
Ref<PyExpression> resolvedRef;
try {
final Integer index = Integer.getInteger(key);
resolvedRef = getElementFromDictLiteral((PyDictLiteralExpression)returnValueExpr, index, resolveContext);
}
catch (NumberFormatException e) {
resolvedRef = getElementFromDictLiteral((PyDictLiteralExpression)returnValueExpr, key, resolveContext);
}
if (resolvedRef != null && !resolvedRef.isNull()) {
return resolvedRef;
}
else if (resolvedRef == null){
allReturnValuesForSure = false;
}
}
else if (returnValueExpr instanceof PyCallExpression) {
if (goDeep) return getElementFromCallExpression(valueExpr, key, resolveContext, false);
}
}
return allReturnValuesForSure ? Ref.create() : null;
}
@Nullable
public static Ref<PyExpression> getElementByIndex(@NotNull PyExpression listTupleExpr, int index) {
boolean noElementsForSure = true;
int seenElementsNumber = 0;
PyExpression[] elements = getElementsFromListOrTuple(listTupleExpr);
for (PyExpression element : elements) {
if (element instanceof PyStarExpression) {
if (!LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON35)) continue;
final PyExpression underStarExpr = ((PyStarExpression)element).getExpression();
if (PyUtil.instanceOf(underStarExpr, PyListLiteralExpression.class, PyTupleExpression.class)) {
PyExpression[] subsequenсeElements = getElementsFromListOrTuple(underStarExpr);
int subsequenceElementIndex = index - seenElementsNumber;
if (subsequenceElementIndex < subsequenсeElements.length) {
return Ref.create(subsequenсeElements[subsequenceElementIndex]);
}
if (noElementsForSure) noElementsForSure = Arrays.stream(subsequenсeElements).anyMatch(it -> it instanceof PyStarExpression);
seenElementsNumber += subsequenсeElements.length;
}
else {
noElementsForSure = false;
break;
}
}
else {
if (index == seenElementsNumber) {
return Ref.create(element);
}
seenElementsNumber++;
}
}
return noElementsForSure ? Ref.create() : null;
}
public static PyExpression[] getElementsFromListOrTuple(@NotNull final PyExpression expression) {
if (expression instanceof PyListLiteralExpression) {
return ((PyListLiteralExpression)expression).getElements();
}
else if (expression instanceof PyTupleExpression) {
return ((PyTupleExpression)expression).getElements();
}
else if (expression instanceof PyStringLiteralExpression) {
String value = ((PyStringLiteralExpression)expression).getStringValue();
if (value != null) {
// Strings might be packed as well as dicts, so we need to resolve somehow to string element.
// But string element isn't PyExpression so I decided to resolve to PyStringLiteralExpression for
// every string element
PyExpression[] result = new PyExpression[value.length()];
for (int i = 0; i < value.length(); i++) {
result[i] = expression;
}
return result;
}
}
return PyExpression.EMPTY_ARRAY;
}
@NotNull
private static List<PyStarArgument> getStarArguments(@NotNull PyArgumentList argumentList, boolean isKeyword) {
return Arrays.asList(argumentList.getArguments()).stream()
return Arrays.stream(argumentList.getArguments())
.map(expression -> PyUtil.as(expression, PyStarArgument.class))
.filter(argument -> argument != null && argument.isKeyword() == isKeyword).collect(Collectors.toList());
}
@@ -162,16 +386,17 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
return null;
}
boolean isKeyWordSubstitution = myChunk.getMappingKey() != null;
return isKeyWordSubstitution ? resolveKeywordPercent(rightExpression) : resolvePositionalPercent(rightExpression);
return isKeyWordSubstitution ? resolveKeywordPercent(rightExpression, myChunk.getMappingKey()) : resolvePositionalPercent(rightExpression);
}
return null;
}
@Nullable
private PsiElement resolveKeywordPercent(@NotNull PyExpression expression) {
private PyExpression resolveKeywordPercent(@NotNull PyExpression expression, @NotNull String key) {
final PyExpression containedExpr = PyPsiUtils.flattenParens(expression);
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
if (containedExpr instanceof PyDictLiteralExpression) {
final Ref<PsiElement> resolvedRef = resolveDictLiteralExpression((PyDictLiteralExpression)containedExpr);
final Ref<PyExpression> resolvedRef = getElementFromDictLiteral((PyDictLiteralExpression)containedExpr, key, resolveContext);
return resolvedRef != null ? resolvedRef.get() : containedExpr;
}
else if (PyUtil.instanceOf(containedExpr, PyLiteralExpression.class, PySetLiteralExpression.class,
@@ -179,7 +404,10 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
return null;
}
else if (containedExpr instanceof PyCallExpression) {
return resolveDictCall((PyCallExpression)containedExpr);
if (myChunk.getMappingKey() != null) {
Ref<PyExpression> elementRef = resolveDictCall((PyCallExpression)containedExpr, myChunk.getMappingKey(), true);
if (elementRef != null) return elementRef.get();
}
}
return containedExpr;
}
@@ -208,7 +436,7 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
}
@Nullable
private PsiElement resolveNotNestedBinaryExpression(PyBinaryExpression containedExpression) {
private PsiElement resolveNotNestedBinaryExpression(@NotNull PyBinaryExpression containedExpression) {
PyExpression left = containedExpression.getLeftExpression();
PyExpression right = containedExpression.getRightExpression();
if (left instanceof PyParenthesizedExpression) {
@@ -219,20 +447,17 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
if (leftTupleLength > myPosition) {
return leftTupleElements[myPosition];
}
if (right instanceof PyParenthesizedExpression) {
PyExpression rightTuple = PyPsiUtils.flattenParens(right);
if (rightTuple instanceof PyTupleExpression) {
PyExpression[] rightTupleElements = ((PyTupleExpression)rightTuple).getElements();
int rightLength = rightTupleElements.length;
if (leftTupleLength + rightLength > myPosition)
return rightTupleElements[myPosition - leftTupleLength];
}
if (right instanceof PyTupleExpression) {
PyExpression[] rightTupleElements = ((PyTupleExpression)right).getElements();
int rightLength = rightTupleElements.length;
if (leftTupleLength + rightLength > myPosition) return rightTupleElements[myPosition - leftTupleLength];
}
}
}
return containedExpression;
}
@Nullable
private static PyArgumentList getArgumentList(final PsiElement original) {
final PsiElement pyReferenceExpression = PsiTreeUtil.getParentOfType(original, PyReferenceExpression.class);
@@ -240,55 +465,36 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
}
@Nullable
private Ref<PsiElement> resolveKeywordStarExpression(@NotNull PyStarArgument starArgument) {
private Ref<PyExpression> resolveKeywordStarExpression(@NotNull PyStarArgument starArgument) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
// TODO: support call, reference expressions here
final PyDictLiteralExpression dictExpr = PsiTreeUtil.getChildOfType(starArgument, PyDictLiteralExpression.class);
return dictExpr != null ? resolveDictLiteralExpression(dictExpr) : null;
return dictExpr != null ? getElementFromDictLiteral(dictExpr, myChunk.getMappingKey(), resolveContext) : null;
}
@Nullable
private Ref<PsiElement> resolvePositionalStarExpression(@NotNull PyStarArgument starArgument, int argumentPosition) {
final PyExpression expr = PsiTreeUtil.getChildOfAnyType(starArgument, PyListLiteralExpression.class, PyParenthesizedExpression.class,
PyStringLiteralExpression.class);
private Ref<PyExpression> resolvePositionalStarExpression(@NotNull PyStarArgument starArgument, int argumentPosition) {
final PyExpression expr = PyPsiUtils.flattenParens(PsiTreeUtil.getChildOfAnyType(starArgument, PyListLiteralExpression.class, PyParenthesizedExpression.class,
PyStringLiteralExpression.class));
if (expr == null) {
return Ref.create(starArgument);
}
final int position = (myChunk.getPosition() != null ? myChunk.getPosition() : myPosition) - argumentPosition;
final PyExpression[] elements;
if (expr instanceof PyListLiteralExpression) {
elements = ((PyListLiteralExpression)expr).getElements();
}
else if (expr instanceof PyParenthesizedExpression) {
final PyExpression expression = PyPsiUtils.flattenParens(expr);
final PyTupleExpression tupleExpr = PyUtil.as(expression, PyTupleExpression.class);
if (tupleExpr == null) {
return Ref.create(starArgument);
}
elements = tupleExpr.getElements();
}
else if (expr instanceof PyStringLiteralExpression) {
if (position < ((PyStringLiteralExpression)expr).getStringValue().length()) {
return Ref.create(expr);
}
return Ref.create();
}
else {
return null;
}
return position < elements.length ? Ref.create(elements[position]) : Ref.create();
return getElementByIndex(expr, position);
}
@Nullable
private Ref<PsiElement> resolveDictLiteralExpression(PyDictLiteralExpression expression) {
private Ref<PyExpression> getElementFromDictLiteral(@NotNull PyDictLiteralExpression expression,
@NotNull String mappingKey,
@NotNull PyResolveContext resolveContext) {
final PyKeyValueExpression[] keyValueExpressions = expression.getElements();
if (keyValueExpressions.length == 0) {
return Ref.create();
}
boolean allKeysForSure = true;
for (PyKeyValueExpression keyValueExpression : keyValueExpressions) {
PyExpression keyExpression = keyValueExpression.getKey();
if (keyExpression instanceof PyStringLiteralExpression) {
final PyStringLiteralExpression key = (PyStringLiteralExpression)keyExpression;
if (key.getStringValue().equals(myChunk.getMappingKey())) {
if (key.getStringValue().equals(mappingKey)) {
return Ref.create(keyValueExpression.getValue());
}
}
@@ -296,27 +502,78 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
allKeysForSure = false;
}
}
final LanguageLevel languageLevel = LanguageLevel.forElement(expression);
PyDoubleStarExpression[] starExpressions = PsiTreeUtil.getChildrenOfType(expression, PyDoubleStarExpression.class);
if (languageLevel.isAtLeast(LanguageLevel.PYTHON35) && starExpressions != null) {
for (PyDoubleStarExpression expr : starExpressions) {
PyExpression underStarExpr = PyPsiUtils.flattenParens(expr.getExpression());
if (underStarExpr != null) {
if (underStarExpr instanceof PyDictLiteralExpression) {
return getElementFromDictLiteral((PyDictLiteralExpression)underStarExpr, mappingKey, resolveContext);
}
else if (underStarExpr instanceof PyCallExpression) {
return getElementFromCallExpression((PyCallExpression)underStarExpr, mappingKey, resolveContext, true);
}
}
}
}
return allKeysForSure ? Ref.create() : null;
}
@Nullable
private PsiElement resolveDictCall(@NotNull PyCallExpression expression) {
private Ref<PyExpression> resolveDictCall(@NotNull PyCallExpression expression, @NotNull String key, boolean goDeep) {
final PyExpression callee = expression.getCallee();
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
boolean allKeysForSure = true;
final LanguageLevel languageLevel = LanguageLevel.forElement(expression);
if (callee != null) {
final String name = callee.getName();
if ("dict".equals(name)) {
final PyArgumentList argumentList = expression.getArgumentList();
for (PyExpression arg : expression.getArguments()) {
if (languageLevel.isAtLeast(LanguageLevel.PYTHON35) && goDeep && arg instanceof PyStarExpression) {
PyExpression expr = ((PyStarExpression)arg).getExpression();
if (expr instanceof PyDictLiteralExpression) {
Ref<PyExpression> element = getElementFromDictLiteral((PyDictLiteralExpression)expr, key, resolveContext);
if (element != null) return element;
}
else if (expr instanceof PyCallExpression) {
Ref<PyExpression> element = resolveDictCall((PyCallExpression)expr, key, false);
if (element != null) return element;
}
else {
allKeysForSure = false;
}
}
if (!(arg instanceof PyKeywordArgument)) {
return expression;
allKeysForSure = false;
}
}
final PyArgumentList argumentList = expression.getArgumentList();
if (argumentList != null) {
return argumentList.getKeywordArgument(myChunk.getMappingKey());
PyKeywordArgument argument = argumentList.getKeywordArgument(key);
if (argument != null) {
return Ref.create(argument);
}
else {
return allKeysForSure ? Ref.create() : null;
}
}
}
}
return expression;
return Ref.create(expression);
}
private static PyReturnStatement[] getFunctionReturnValues(@NotNull PyCallExpression callExpression,
@NotNull PyResolveContext resolveContext,
@NotNull TypeEvalContext evalContext) {
final PyCallable callable = callExpression.resolveCalleeFunction(resolveContext);
if (callable instanceof PyFunction && evalContext.maySwitchToAST(callable)) {
PyStatementList statementList = ((PyFunction)callable).getStatementList();
return PyUtil.getAllChildrenOfType(statementList, PyReturnStatement.class);
}
return new PyReturnStatement[0];
}
@NotNull
@@ -546,16 +546,19 @@ public class PyStringFormatInspection extends PyInspection {
// it's true because we set position manually in inspect()
assert chunk.getPosition() != null;
final PsiElement target = new PySubstitutionChunkReference(myFormatExpression, chunk, chunk.getPosition()).resolve();
boolean hasElementIndex = chunk.getMappingKeyElementIndex() != null;
if (target == null) {
final String chunkMapping = chunk.getMappingKey();
registerProblem(myFormatExpression, chunkMapping == null ? PyBundle.message("INSP.too.few.keys") :
PyBundle.message("INSP.unused.mapping", chunkMapping));
if (chunkMapping != null) {
registerProblem(myFormatExpression, hasElementIndex ?
PyBundle.message("INSP.too.few.args.for.fmt.string") :
PyBundle.message("INSP.key.$0.has.no.arg", chunkMapping));
}
else {
registerProblem(myFormatExpression, PyBundle.message("INSP.too.few.args.for.fmt.string"));
}
}
else {
if (chunk.getMappingKeyElementIndex() != null) {
inspectIndexElements(chunk, myFormatExpression, target, target, mappingKey);
}
checkTypesCompatibleForCheckedTypesOnly(myFormatExpression, target, mappingKey);
}
}
@@ -565,171 +568,6 @@ public class PyStringFormatInspection extends PyInspection {
myVisitor.registerProblem(problemTarget, message);
}
private void inspectIndexElements(@NotNull PyStringFormatParser.NewStyleSubstitutionChunk chunk,
@NotNull PyStringLiteralExpression formatExpression,
@NotNull final PsiElement problemElement,
@NotNull PsiElement inspectedElement,
@NotNull String mappingKey) {
final String indexElement = chunk.getMappingKeyElementIndex();
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
if (indexElement != null) {
try {
final Integer index = Integer.valueOf(indexElement);
if (inspectedElement instanceof PyListLiteralExpression) {
inspectListElements(formatExpression, problemElement, index, inspectedElement);
}
if (inspectedElement instanceof PyParenthesizedExpression) {
final PyExpression flatten = PyPsiUtils.flattenParens((PyParenthesizedExpression)inspectedElement);
if (flatten != null) {
inspectIndexElements(chunk, formatExpression, problemElement, flatten, mappingKey);
}
}
else if (inspectedElement instanceof PyTupleExpression) {
final PyExpression[] elements = ((PyTupleExpression)inspectedElement).getElements();
if (index >= elements.length) {
registerProblem(problemElement, PyBundle.message("INSP.too.few.args.for.fmt.string"));
}
else {
checkTypesCompatibleForCheckedTypesOnly(formatExpression, elements[index], indexElement);
}
}
else if (inspectedElement instanceof PyReferenceExpression) {
inspectReferenceExpressionForNumericKey(chunk, formatExpression, (PyReferenceExpression)inspectedElement, mappingKey,
indexElement);
}
else if (inspectedElement instanceof PyCallExpression) {
final int callResultsArgumentsNumber = inspectCallExpression((PyCallExpression)inspectedElement, resolveContext,
myTypeEvalContext, false);
if (callResultsArgumentsNumber <= index) {
registerProblem(inspectedElement, PyBundle.message("INSP.too.few.args.for.fmt.string"));
}
}
else if (inspectedElement instanceof PyDictLiteralExpression) {
inspectDictForKey(formatExpression, inspectedElement, (PyDictLiteralExpression)inspectedElement, mappingKey, index);
}
}
catch (NumberFormatException e) {
if (inspectedElement instanceof PyCallExpression) {
final PyReturnStatement[] returnValues = getFunctionReturnValues((PyCallExpression)inspectedElement, resolveContext,
myTypeEvalContext);
for (PyReturnStatement value : returnValues) {
PyExpression valueExpression = PyPsiUtils.flattenParens(value.getExpression());
if (valueExpression instanceof PyDictLiteralExpression) {
inspectDictForKey(formatExpression, inspectedElement, (PyDictLiteralExpression)valueExpression, mappingKey,
indexElement);
}
else if (valueExpression instanceof PyCallExpression) {
final PyExpression callee = ((PyCallExpression)valueExpression).getCallee();
if (callee != null && callee.getName() != null && callee.getName().equals("dict")) {
final PyExpression[] arguments = ((PyCallExpression)valueExpression).getArguments();
for (PyExpression argument : arguments) {
if (argument instanceof PyKeywordArgument && indexElement.equals(((PyKeywordArgument)argument).getKeyword())) {
checkTypesCompatibleForCheckedTypesOnly(formatExpression, argument, mappingKey);
return;
}
}
registerProblem(inspectedElement, PyBundle.message("INSP.too.few.keys"));
}
}
}
}
else if (inspectedElement instanceof PyReferenceExpression) {
inspectReferenceExpressionForNumericKey(chunk, formatExpression, (PyReferenceExpression)inspectedElement, mappingKey,
indexElement);
}
else if (inspectedElement instanceof PyDictLiteralExpression) {
inspectDictForKey(formatExpression, inspectedElement, (PyDictLiteralExpression)inspectedElement, mappingKey, indexElement);
}
else if (inspectedElement instanceof PyParenthesizedExpression) {
final PyExpression expression = PyPsiUtils.flattenParens((PyExpression)inspectedElement);
if (expression != null) {
inspectIndexElements(chunk, formatExpression, expression, expression, mappingKey);
}
}
}
}
}
private void inspectReferenceExpressionForNumericKey(@NotNull PyStringFormatParser.NewStyleSubstitutionChunk chunk,
@NotNull PyStringLiteralExpression formatExpression,
@NotNull PyReferenceExpression target,
@NotNull String mappingKey,
@NotNull String index) {
if (PyNames.DICT.equals(target.getName())) return;
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
final PsiElement element = target.followAssignmentsChain(resolveContext).getElement();
if (element == target || !(element instanceof PyExpression)) {
return;
}
if (element instanceof PyDictLiteralExpression) {
try {
final Integer ind = Integer.valueOf(index);
inspectDictForKey(formatExpression, target, (PyDictLiteralExpression)element, mappingKey, ind);
}
catch (NumberFormatException e) {
inspectDictForKey(formatExpression, target, (PyDictLiteralExpression)element, mappingKey, index);
}
}
else {
inspectIndexElements(chunk, formatExpression, target, element, mappingKey);
}
}
private void inspectDictForKey(@NotNull PyStringLiteralExpression formatExpression,
@NotNull PsiElement problemElement,
@NotNull PyDictLiteralExpression dict,
@NotNull String mappingKey,
@NotNull Integer index) {
final PyKeyValueExpression[] elements = dict.getElements();
for (PyKeyValueExpression element : elements) {
final PyExpression key = element.getKey();
if (key instanceof PyNumericLiteralExpression && new Long(index).equals(((PyNumericLiteralExpression)key).getLongValue())) {
checkTypesCompatibleForCheckedTypesOnly(formatExpression, key, mappingKey);
return;
}
}
registerProblem(problemElement, PyBundle.message("INSP.too.few.keys"));
}
private void inspectDictForKey(@NotNull PyStringLiteralExpression formatExpression,
@NotNull PsiElement problemElement,
@NotNull PyDictLiteralExpression dict,
@NotNull String mappingKey,
@NotNull String indexElement) {
final PyKeyValueExpression[] elements = dict.getElements();
for (PyKeyValueExpression element : elements) {
final PyExpression key = element.getKey();
if (key instanceof PyStringLiteralExpression && indexElement.equals(((PyStringLiteralExpression)key).getStringValue())) {
checkTypesCompatibleForCheckedTypesOnly(formatExpression, key, mappingKey);
return;
}
}
registerProblem(problemElement, PyBundle.message("INSP.too.few.keys"));
}
private void inspectListElements(@NotNull final PyStringLiteralExpression formatExpression,
@NotNull final PsiElement problemElement,
@NotNull final Integer index,
@NotNull final PsiElement target) {
final PyExpression[] elements = ((PyListLiteralExpression)target).getElements();
if (elements.length > index) {
checkTypesCompatibleForCheckedTypesOnly(formatExpression, elements[index], String.valueOf(index));
}
else {
registerProblem(problemElement, PyBundle.message("INSP.too.few.args.for.fmt.string"));
}
}
private void checkTypesCompatibleForCheckedTypesOnly(@NotNull PyStringLiteralExpression anchor,
@NotNull PsiElement target,
@NotNull String mappingKey) {
@@ -8,7 +8,7 @@ def f(mode):
elif mode == "b":
return True
<warning descr="Too few mapping keys">"{}{}"</warning>.format(f("i"))
<warning descr="Too few mapping keys">"{}{}"</warning>.format(f("f"))
<warning descr="Too few mapping keys">"{}{}"</warning>.format(f("s"))
<warning descr="Too few mapping keys">"{}{}"</warning>.format(f("b"))
<warning descr="Too few arguments for format string">"{}{}"</warning>.format(f("i"))
<warning descr="Too few arguments for format string">"{}{}"</warning>.format(f("f"))
<warning descr="Too few arguments for format string">"{}{}"</warning>.format(f("s"))
<warning descr="Too few arguments for format string">"{}{}"</warning>.format(f("b"))
@@ -1,2 +1,2 @@
print("{foo}".format(**dict({'foo': 'bar'})))
<warning descr="Too few mapping keys">"{}"</warning>.format()
<warning descr="Too few arguments for format string">"{}"</warning>.format()
@@ -1 +1 @@
print (<warning descr="Mapping key \"fst\" is unused">"first is {fst}"</warning>.format(**{1: "3"})<EOLError descr="')' expected"></EOLError>
print (<warning descr="Key 'fst' has no following argument">"first is {fst}"</warning>.format(**{1: "3"})<EOLError descr="')' expected"></EOLError>
@@ -1 +1 @@
print(<warning descr="Mapping key \"foo\" is unused">"{foo}"</warning>.format(**{}))
print(<warning descr="Key 'foo' has no following argument">"{foo}"</warning>.format(**{}))
@@ -1,5 +1,5 @@
"{foo[a]}".format(foo={"a": 1})
"{foo[b]}".format(foo=<warning descr="Too few mapping keys">{"a": 1}</warning>)
<warning descr="Too few arguments for format string">"{foo[b]}"</warning>.format(foo={"a": 1})
"{foo[1]}".format(foo={1: 1})
"{foo[2]}".format(foo=<warning descr="Too few mapping keys">{1: 1}</warning>)
<warning descr="Too few arguments for format string">"{foo[2]}"</warning>.format(foo={1: 1})
@@ -2,16 +2,16 @@ def f():
return [1, 2, 3]
"{foo[1]}".format(foo=f())
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">f()</warning>)
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=f())
def g():
return 1, 2, 3
"{foo[1]:d}".format(foo=g())
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">g()</warning>)
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=g())
def ff():
return g()
"{foo[1]}".format(foo=g())
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">ff()</warning>)
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=ff())
@@ -2,11 +2,11 @@ def d():
return {"a": 1}
"{foo[a]}".format(foo=d())
"{foo[b]}".format(foo=<warning descr="Too few mapping keys">d()</warning>)
<warning descr="Too few arguments for format string">"{foo[b]}"</warning>.format(foo=d())
def d_dict():
return dict(a=1)
"{foo[a]}".format(foo=d_dict())
"{foo[b]}".format(foo=<warning descr="Too few mapping keys">d_dict()</warning>)
<warning descr="Too few arguments for format string">"{foo[b]}"</warning>.format(foo=d_dict())
@@ -1,2 +1,2 @@
"{foo[1]}".format(foo=[0, 1, 2])
"{foo[2]}".format(foo=<warning descr="Too few arguments for format string">[0, 1]</warning>)
<warning descr="Too few arguments for format string">"{foo[2]}"</warning>.format(foo=[0, 1])
@@ -4,7 +4,7 @@
"{foo[a]}".format(foo=({"a": 1}))
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">(1, 2, 3)</warning>)
"{foo[3]}".format(foo=(<warning descr="Too few mapping keys">{1: 1}</warning>))
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">([1, 2, 3])</warning>)
"{foo[b]}".format(foo=(<warning descr="Too few mapping keys">{"a": 1}</warning>))
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=(1, 2, 3))
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=({1: 1}))
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=([1, 2, 3]))
<warning descr="Too few arguments for format string">"{foo[b]}"</warning>.format(foo=({"a": 1}))
@@ -1,7 +1,7 @@
list = [1, 2, 3]
"{foo[1]}".format(foo=list)
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">list</warning>)
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=list)
tuple = (1, 2, 3)
"{foo[1]}".format(foo=tuple)
"{foo[3]}".format(foo=<warning descr="Too few arguments for format string">tuple</warning>)
<warning descr="Too few arguments for format string">"{foo[3]}"</warning>.format(foo=tuple)
@@ -1,7 +1,7 @@
d = {"a": 1}
"{foo[a]}".format(foo=d)
"{foo[b]}".format(foo=<warning descr="Too few mapping keys">d</warning>)
<warning descr="Too few arguments for format string">"{foo[b]}"</warning>.format(foo=d)
d_num = {1: 1}
"{foo[1]}".format(foo=d_num)
"{foo[2]}".format(foo=<warning descr="Too few mapping keys">d_num</warning>)
<warning descr="Too few arguments for format string">"{foo[2]}"</warning>.format(foo=d_num)
@@ -3,4 +3,4 @@ def f():
'{foo}'.format(**f())
<warning descr="Too few mapping keys">"{}"</warning>.format()
<warning descr="Too few arguments for format string">"{}"</warning>.format()
@@ -1,4 +1,4 @@
ref = {"fst": 1, "snd": 2}
print "first is {fst}, second is {snd}".format(**ref)
<warning descr="Too few mapping keys">"{}"</warning>.format()
<warning descr="Too few arguments for format string">"{}"</warning>.format()
@@ -1 +1 @@
print(<warning descr="Too few mapping keys">'{}'</warning>.format(foo='foo'))
print(<warning descr="Too few arguments for format string">'{}'</warning>.format(foo='foo'))
@@ -1 +1 @@
<warning descr="Too few mapping keys">'{3}, {1}, {0}'</warning>.format(*'abc')
<warning descr="Too few arguments for format string">'{3}, {1}, {0}'</warning>.format(*'abc')
@@ -1,2 +1,2 @@
<warning descr="Too few mapping keys">"{} {}"</warning>.format(1)
<warning descr="Too few mapping keys">'{}'</warning>.format()
<warning descr="Too few arguments for format string">"{} {}"</warning>.format(1)
<warning descr="Too few arguments for format string">'{}'</warning>.format()
@@ -1,2 +1,2 @@
<warning descr="Too few mapping keys">'Hello, {1}'</warning>.format('World')
<warning descr="Too few mapping keys">'Hello, {} {}!'</warning>.format('World')
<warning descr="Too few arguments for format string">'Hello, {1}'</warning>.format('World')
<warning descr="Too few arguments for format string">'Hello, {} {}!'</warning>.format('World')
@@ -1,2 +1,2 @@
<warning descr="Mapping key \"name\" is unused">"{name}"</warning>.format()
<warning descr="Mapping key \"foo\" is unused">'{foo}'</warning>.format(boo=1)
<warning descr="Key 'name' has no following argument">"{name}"</warning>.format()
<warning descr="Key 'foo' has no following argument">'{foo}'</warning>.format(boo=1)