diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 8dacfd3ec597..e333e864cfa2 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -135,7 +135,40 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } else if ("__builtin__.tuple.__add__".equals(qname) && callSite instanceof PyBinaryExpression) { - return getTupleConcatenationResultType(((PyBinaryExpression)callSite), context); + return getTupleConcatenationResultType((PyBinaryExpression)callSite, context); + } + else if ("__builtin__.tuple.__mul__".equals(qname) && callSite instanceof PyBinaryExpression) { + return getTupleMultiplicationResultType((PyBinaryExpression)callSite, context); + } + } + return null; + } + + @Nullable + private static PyType getTupleMultiplicationResultType(@NotNull PyBinaryExpression multiplication, @NotNull TypeEvalContext context) { + final PyTupleType leftTupleType = as(context.getType(multiplication.getLeftExpression()), PyTupleType.class); + if (leftTupleType == null) { + return null; + } + PyExpression rightExpression = multiplication.getRightExpression(); + if (rightExpression instanceof PyReferenceExpression) { + final PsiElement target = ((PyReferenceExpression)rightExpression).getReference().resolve(); + if (target instanceof PyTargetExpression) { + rightExpression = ((PyTargetExpression)target).findAssignedValue(); + } + } + if (rightExpression instanceof PyNumericLiteralExpression && ((PyNumericLiteralExpression)rightExpression).isIntegerLiteral()) { + final int multiplier = ((PyNumericLiteralExpression)rightExpression).getBigIntegerValue().intValue(); + final int originalSize = leftTupleType.getElementCount(); + // Heuristic + if (originalSize * multiplier <= 20) { + final PyType[] elementTypes = new PyType[leftTupleType.getElementCount() * multiplier]; + for (int i = 0; i < multiplier; i++) { + for (int j = 0; j < originalSize; j++) { + elementTypes[i * originalSize + j] = leftTupleType.getElementType(j); + } + } + return PyTupleType.create(multiplication, elementTypes); } } return null; diff --git a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java index 9e61faabdc65..187803f4cfa3 100644 --- a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java @@ -102,39 +102,25 @@ public class PyStringFormatInspection extends PyInspection { final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(problemTarget); final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext); - //in case tuple multiplies integer PY-4647 - if (rightExpression instanceof PyBinaryExpression) { - PyBinaryExpression binaryExpression = (PyBinaryExpression)rightExpression; - if (binaryExpression.getOperator() == PyTokenTypes.MULT && binaryExpression.getLeftExpression() instanceof PyParenthesizedExpression - && binaryExpression.getRightExpression() instanceof PyNumericLiteralExpression) { - PyParenthesizedExpression parenthesizedExpression = (PyParenthesizedExpression)binaryExpression.getLeftExpression(); - if (parenthesizedExpression.getContainedExpression() instanceof PyTupleExpression) { - PyExpression[] tupleElements = ((PyTupleExpression)parenthesizedExpression.getContainedExpression()).getElements(); - final PyExpression expression = ((PyBinaryExpression)rightExpression).getRightExpression(); - if (expression != null) { - return ((PyNumericLiteralExpression)expression).getBigIntegerValue().intValue() * tupleElements.length; - } - } - } - } final String s = myFormatSpec.get("1"); if (PyUtil.instanceOf(rightExpression, SIMPLE_RHS_EXPRESSIONS)) { if (s != null) { - assert rightExpression != null; - PyType right_type = myTypeEvalContext.getType(rightExpression); - if (right_type instanceof PySubscriptableType) { - PySubscriptableType tuple_type = (PySubscriptableType)right_type; - for (int i=0; i <= tuple_type.getElementCount(); i += 1) { - PyType elementType = tuple_type.getElementType(i); + final PyType rightType = myTypeEvalContext.getType(rightExpression); + if (rightType instanceof PySubscriptableType) { + final PySubscriptableType tupleType = (PySubscriptableType)rightType; + for (int i = 0; i <= tupleType.getElementCount(); i += 1) { + final PyType elementType = tupleType.getElementType(i); if (elementType != null) { final String typeName = myFormatSpec.get(String.valueOf(i + 1)); final PyType type = typeName != null ? PyTypeParser.getTypeByName(problemTarget, typeName) : null; checkTypeCompatible(problemTarget, elementType, type); } } - return tuple_type.getElementCount(); + return tupleType.getElementCount(); + } + else { + checkExpressionType(rightExpression, s, problemTarget); } - else checkExpressionType(rightExpression, s, problemTarget); } return 1; } diff --git a/python/testData/inspections/PyStringFormatInspection1/test.py b/python/testData/inspections/PyStringFormatInspection1/test.py index 3ed0e3edb65f..92212324a230 100644 --- a/python/testData/inspections/PyStringFormatInspection1/test.py +++ b/python/testData/inspections/PyStringFormatInspection1/test.py @@ -10,4 +10,7 @@ my_dict['tmp'] = 'classes %(claz)s' % t * num) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 4877cbcb6d7e..d4f6acda8fd3 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -848,6 +848,11 @@ public class PyTypeTest extends PyTestCase { "expr = (1,) + (True, 'spam') + ()"); } + public void testTupleMultiplication() { + doTest("(int, bool, int, bool)", + "expr = (1, False) * 2"); + } + public void testConstructorUnification() { doTest("C[int]", "class C(object):\n" +