mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
Valentina Kiryushkina
parent
f608274755
commit
1dfb4dcdee
@@ -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) {
|
||||
|
||||
+4
-4
@@ -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
-1
@@ -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
@@ -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(**{}))
|
||||
+2
-2
@@ -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})
|
||||
+3
-3
@@ -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
-2
@@ -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
-1
@@ -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
-4
@@ -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}))
|
||||
+2
-2
@@ -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)
|
||||
+2
-2
@@ -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
@@ -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')
|
||||
+2
-2
@@ -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)
|
||||
Reference in New Issue
Block a user