mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-8325 Analyze call expression return type to determine number of arguments Add tests
to squash
This commit is contained in:
@@ -40,6 +40,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.jetbrains.python.inspections.PyStringFormatParser.filterSubstitutions;
|
||||
import static com.jetbrains.python.inspections.PyStringFormatParser.parsePercentFormat;
|
||||
@@ -161,30 +162,9 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
return inspectArguments((PyExpression)pyElement, problemTarget);
|
||||
}
|
||||
else if (rightExpression instanceof PyCallExpression) {
|
||||
final PyCallExpression call = (PyCallExpression)rightExpression;
|
||||
|
||||
final IntSummaryStatistics statistics = call.multiResolveCalleeFunction(resolveContext)
|
||||
.stream()
|
||||
.map(callable -> callable.getCallType(myTypeEvalContext, call))
|
||||
.collect(
|
||||
Collectors.summarizingInt(
|
||||
callType -> {
|
||||
if (callType instanceof PyTupleType) {
|
||||
return ((PyTupleType)callType).getElementCount();
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (statistics.getMin() == statistics.getMax()) {
|
||||
return statistics.getMin();
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
final PyExpression callee = ((PyCallExpression)rightExpression).getCallee();
|
||||
if (callee != null && "dict".equals(callee.getName())) return 1;
|
||||
return inspectCallExpression((PyCallExpression)rightExpression, resolveContext, myTypeEvalContext, true);
|
||||
}
|
||||
else if (rightExpression instanceof PyParenthesizedExpression) {
|
||||
final PyExpression rhs = ((PyParenthesizedExpression)rightExpression).getContainedExpression();
|
||||
@@ -590,36 +570,6 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
private int inspectCallExpression(@NotNull PyCallExpression callExpression, @NotNull PyResolveContext resolveContext) {
|
||||
final PyReturnStatement[] returnStatements = getFunctionReturnValues(callExpression, resolveContext);
|
||||
int expressionsSize = -1;
|
||||
for (PyReturnStatement returnStatement : returnStatements) {
|
||||
if (returnStatement.getExpression() instanceof PyCallExpression) {
|
||||
return -1;
|
||||
}
|
||||
final int argumentsSize = Math.max(PyUtil.flattenedParensAndTuples(returnStatement.getExpression()).size(),
|
||||
PyUtil.flattenedParensAndLists(returnStatement.getExpression()).size());
|
||||
if (expressionsSize < 0) {
|
||||
expressionsSize = argumentsSize;
|
||||
}
|
||||
if (expressionsSize != argumentsSize) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return expressionsSize;
|
||||
}
|
||||
|
||||
|
||||
private PyReturnStatement[] getFunctionReturnValues(@NotNull PyCallExpression callExpression,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
final PyCallable callable = callExpression.resolveCalleeFunction(resolveContext);
|
||||
if (callable instanceof PyFunction && myTypeEvalContext.maySwitchToAST(callable)) {
|
||||
PyStatementList statementList = ((PyFunction)callable).getStatementList();
|
||||
return PyUtil.getAllChildrenOfType(statementList, PyReturnStatement.class);
|
||||
}
|
||||
return new PyReturnStatement[0];
|
||||
}
|
||||
|
||||
private void registerProblem(@NotNull PsiElement problemTarget, @NotNull final String message) {
|
||||
myProblemRegister = true;
|
||||
myVisitor.registerProblem(problemTarget, message);
|
||||
@@ -660,7 +610,8 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
indexElement);
|
||||
}
|
||||
else if (inspectedElement instanceof PyCallExpression) {
|
||||
final int callResultsArgumentsNumber = inspectCallExpression((PyCallExpression)inspectedElement, resolveContext);
|
||||
final int callResultsArgumentsNumber = inspectCallExpression((PyCallExpression)inspectedElement, resolveContext,
|
||||
myTypeEvalContext, false);
|
||||
if (callResultsArgumentsNumber <= index) {
|
||||
registerProblem(inspectedElement, PyBundle.message("INSP.too.few.args.for.fmt.string"));
|
||||
}
|
||||
@@ -671,10 +622,10 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
if (inspectedElement instanceof PyCallExpression) {
|
||||
final PyReturnStatement[] returnValues = getFunctionReturnValues((PyCallExpression)inspectedElement, resolveContext);
|
||||
final PyReturnStatement[] returnValues = getFunctionReturnValues((PyCallExpression)inspectedElement, resolveContext,
|
||||
myTypeEvalContext);
|
||||
for (PyReturnStatement value : returnValues) {
|
||||
PyExpression valueExpression = value.getExpression();
|
||||
valueExpression = PyPsiUtils.flattenParens(valueExpression);
|
||||
PyExpression valueExpression = PyPsiUtils.flattenParens(value.getExpression());
|
||||
if (valueExpression instanceof PyDictLiteralExpression) {
|
||||
inspectDictForKey(formatExpression, inspectedElement, (PyDictLiteralExpression)valueExpression, mappingKey,
|
||||
indexElement);
|
||||
@@ -818,6 +769,87 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
static int inspectCallExpression(@NotNull PyCallExpression callExpression,
|
||||
@NotNull PyResolveContext resolveContext,
|
||||
@NotNull TypeEvalContext evalContext,
|
||||
boolean isPercent) {
|
||||
final IntSummaryStatistics statistics = callExpression.multiResolveCalleeFunction(resolveContext)
|
||||
.stream()
|
||||
.map(callable -> callable.getCallType(evalContext, callExpression))
|
||||
.collect(
|
||||
Collectors.summarizingInt(
|
||||
callType -> {
|
||||
if (callType instanceof PyTupleType) {
|
||||
return ((PyTupleType)callType).getElementCount();
|
||||
}
|
||||
else if (callType instanceof PyCollectionTypeImpl
|
||||
&& ((PyCollectionTypeImpl)callType).getElementTypes(evalContext).size() == 1) {
|
||||
if (isPercent) return 1;
|
||||
|
||||
final PyClass pyClass = ((PyCollectionTypeImpl)callType).getPyClass();
|
||||
if ("list".equals(pyClass.getName())) {
|
||||
final PyReturnStatement[] returnStatements = getFunctionReturnValues(callExpression, resolveContext, evalContext);
|
||||
int expressionsSize = -1;
|
||||
for (PyReturnStatement returnStatement : returnStatements) {
|
||||
if (returnStatement.getExpression() instanceof PyCallExpression) {
|
||||
return -1;
|
||||
}
|
||||
final int argumentsSize = PyUtil.flattenedParensAndLists(returnStatement.getExpression()).size();
|
||||
if (expressionsSize < 0) {
|
||||
expressionsSize = argumentsSize;
|
||||
}
|
||||
if (expressionsSize != argumentsSize) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return expressionsSize;
|
||||
}
|
||||
}
|
||||
else if (callType instanceof PyNoneType) {
|
||||
return 1;
|
||||
}
|
||||
else if (callType instanceof PyClassType) {
|
||||
final PyClassType setType = PyBuiltinCache.getInstance(callExpression).getSetType();
|
||||
final PyClassType tupleType = PyBuiltinCache.getInstance(callExpression).getTupleType();
|
||||
|
||||
if (!callType.equals(tupleType) &&
|
||||
(callType.equals(setType) && isPercent
|
||||
|| PyBuiltinCache.getInstance(callExpression).isBuiltin(((PyClassType)callType).getPyClass()))) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (callType instanceof PyUnionType) {
|
||||
if (((PyUnionType)callType).getMembers().stream().allMatch(PyType::isBuiltin)) return 1;
|
||||
}
|
||||
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (statistics.getMin() == statistics.getMax()) {
|
||||
return statistics.getMin();
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
|
||||
super(holder, session);
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
def f(mode):
|
||||
if mode == "i":
|
||||
return 1
|
||||
elif mode == "f":
|
||||
return 1.0
|
||||
elif mode == "s":
|
||||
return ""
|
||||
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"))
|
||||
@@ -0,0 +1,28 @@
|
||||
def bar():
|
||||
return 1
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return 1.0
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return ""
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return True
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return []
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return {}
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
def bar():
|
||||
return set()
|
||||
"%s %s" % <warning descr="Too few arguments for format string">bar()</warning>
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
def f(mode):
|
||||
if mode == "i":
|
||||
return 1
|
||||
elif mode == "f":
|
||||
return 1.0
|
||||
elif mode == "s":
|
||||
return ""
|
||||
elif mode == "b":
|
||||
return True
|
||||
elif mode == "l":
|
||||
return []
|
||||
elif mode == "d":
|
||||
return {}
|
||||
elif mode == "set":
|
||||
return set()
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("i")</warning>
|
||||
|
||||
def bar():
|
||||
return 1.0
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("f")</warning>
|
||||
|
||||
def bar():
|
||||
return ""
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("s")</warning>
|
||||
|
||||
def bar():
|
||||
return True
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("b")</warning>
|
||||
|
||||
def bar():
|
||||
return []
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("l")</warning>
|
||||
|
||||
def bar():
|
||||
return {}
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("d")</warning>
|
||||
|
||||
def bar():
|
||||
return set()
|
||||
"%s %s" % <warning descr="Too few arguments for format string">f("set")</warning>
|
||||
|
||||
@@ -96,6 +96,98 @@ public class PyStringFormatInspectionTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleStringWithPercentSymbol() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStylePackedAndNonPackedArgs() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleEmptyDictArg() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleDictLiteralExprInsideDictCall() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStylePositionalSubstitutionWithDictArg() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStylePackedReference() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStylePackedFunctionCall() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleStringRegularExpression() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleStringMapArg() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleDictLiteralWithReferenceKeys() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleDictLiteralWithNumericKeys() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNewStyleCallExpressionArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringWithFormatStringReplacementSymbols() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringPositionalWithEmptyDictArg() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringWithDictElement() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringWithDictCall() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringWithDictArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringPositionalListArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringPositionalDictArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringKeywordSetArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringKeywordListArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringCallUnionArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentStringCallArgument() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
myFixture.configureByFile(TEST_DIRECTORY + getTestName(false) + ".py");
|
||||
myFixture.enableInspections(PyStringFormatInspection.class);
|
||||
|
||||
Reference in New Issue
Block a user