From 3c8bc79731db109d6017fb5c87e73f87fd39ad39 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sat, 12 Mar 2016 19:46:30 +0300 Subject: [PATCH] PY-18762 Support annotations for homogeneous tuples in form Tuple[type, ...] Introduce the concept of homogeneous tuple in our type system. We can't infer such types ourselves, but handle them during type checking. --- .../codeInsight/PyTypingTypeProvider.java | 21 ++++++-- .../documentation/PyTypeModelBuilder.java | 11 ++-- .../python/psi/types/PyTupleType.java | 53 +++++++++++++------ .../com/jetbrains/python/PyTypingTest.java | 19 +++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java index e68a17cbafdb..b67ed8a3e6f6 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java @@ -39,6 +39,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.intellij.util.containers.ContainerUtil.list; import static com.jetbrains.python.psi.PyUtil.as; /** @@ -389,12 +390,13 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } if (!paramListTypePositions.isEmpty()) { - if (!("typing.Callable".equals(qualifiedName) && paramListTypePositions.equals(Collections.singletonList(0)))) { + if (!("typing.Callable".equals(qualifiedName) && paramListTypePositions.equals(list(0)))) { return null; } } if (!ellipsisTypePositions.isEmpty()) { - if (!("typing.Callable".equals(qualifiedName) && ellipsisTypePositions.equals(Collections.singletonList(0)))) { + if (!("typing.Callable".equals(qualifiedName) && ellipsisTypePositions.equals(list(0)) || + "typing.Tuple".equals(qualifiedName) && ellipsisTypePositions.equals(list(1)) && elementTypes.size() == 2)) { return null; } } @@ -415,6 +417,9 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } if ("typing.Tuple".equals(qualifiedName)) { + if (elementTypes.get(1) instanceof PyTypeParser.EllipsisType) { + return PyTupleType.createHomogeneous(resolved, elementTypes.get(0)); + } return PyTupleType.create(resolved, elementTypes.toArray(new PyType[elementTypes.size()])); } final PyType builtinCollection = getBuiltinCollection(resolved); @@ -533,7 +538,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { final PyType returnType = getType(returnTypeExpr, context); return new PyCallableTypeImpl(parameters, returnType); } - if (parametersExpr instanceof PyNoneLiteralExpression && ((PyNoneLiteralExpression)parametersExpr).isEllipsis()) { + if (isEllipsis(parametersExpr)) { return new PyCallableTypeImpl(null, getType(returnTypeExpr, context)); } } @@ -543,6 +548,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return null; } + private static boolean isEllipsis(@NotNull PyExpression parametersExpr) { + return parametersExpr instanceof PyNoneLiteralExpression && ((PyNoneLiteralExpression)parametersExpr).isEllipsis(); + } + @Nullable private static PyType getUnionType(@NotNull PsiElement element, @NotNull Context context) { if (element instanceof PySubscriptionExpression) { @@ -616,6 +625,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { final PyClass cls = ((PyClassType)operandType).getPyClass(); final List indexTypes = getIndexTypes(subscriptionExpr, context); if (PyNames.TUPLE.equals(cls.getQualifiedName())) { + if (indexExpr instanceof PyTupleExpression) { + final PyExpression[] elements = ((PyTupleExpression)indexExpr).getElements(); + if (elements.length == 2 && isEllipsis(elements[1])) { + return PyTupleType.createHomogeneous(element, indexTypes.get(0)); + } + } return PyTupleType.create(element, indexTypes.toArray(new PyType[indexTypes.size()])); } else if (indexExpr != null) { diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index c7d42130cf33..7a71fa1f443e 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -129,9 +129,11 @@ public class PyTypeModelBuilder { static class TupleType extends TypeModel { private final List members; + private final boolean homogeneous; - public TupleType(List members) { + public TupleType(List members, boolean homogeneous) { this.members = members; + this.homogeneous = homogeneous; } @Override @@ -244,11 +246,11 @@ public class PyTypeModelBuilder { else if (type instanceof PyTupleType) { final List elementModels = new ArrayList(); final PyTupleType tupleType = (PyTupleType)type; - for (int i = 0; i < tupleType.getElementCount(); i++) { + for (int i = 0; i < (tupleType.isHomogeneous() ? 1 : tupleType.getElementCount()); i++) { final PyType elementType = tupleType.getElementType(i); elementModels.add(build(elementType, true)); } - result = new TupleType(elementModels); + result = new TupleType(elementModels, tupleType.isHomogeneous()); } if (result == null) { result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); @@ -477,6 +479,9 @@ public class PyTypeModelBuilder { public void tuple(TupleType type) { add("Tuple["); processList(type.members, ", "); + if (type.homogeneous) { + add(", ..."); + } add("]"); } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTupleType.java b/python/src/com/jetbrains/python/psi/types/PyTupleType.java index 1a73bd9b3c78..d3ed423c5757 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTupleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyTupleType.java @@ -17,7 +17,6 @@ package com.jetbrains.python.psi.types; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; -import com.intellij.util.Function; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyExpression; @@ -33,33 +32,46 @@ import java.util.Arrays; */ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType { private final PyType[] myElementTypes; - - PyTupleType(@NotNull PyClass tupleClass, PsiElement anchor, PyType[] elementTypes) { - super(tupleClass, false); - myElementTypes = elementTypes; - } + private final boolean myHomogeneous; @Nullable - public static PyTupleType create(PsiElement anchor, PyType[] elementTypes) { + public static PyTupleType create(@NotNull PsiElement anchor, @NotNull PyType[] elementTypes) { PyClass tuple = PyBuiltinCache.getInstance(anchor).getClass(PyNames.TUPLE); if (tuple != null) { - return new PyTupleType(tuple, anchor, elementTypes); + return new PyTupleType(tuple, elementTypes, false); } return null; } - public PyTupleType(PyTupleType origin, PyType[] elementTypes) { - super(origin.getPyClass(), false); + @Nullable + public static PyTupleType createHomogeneous(@NotNull PsiElement anchor, @Nullable PyType elementType) { + PyClass tuple = PyBuiltinCache.getInstance(anchor).getClass(PyNames.TUPLE); + if (tuple != null) { + return new PyTupleType(tuple, new PyType[] {elementType}, true); + } + return null; + } + + PyTupleType(@NotNull PyClass tupleClass, @NotNull PyType[] elementTypes, boolean homogeneous) { + super(tupleClass, false); myElementTypes = elementTypes; + myHomogeneous = homogeneous; + } + + public PyTupleType(@NotNull PyTupleType origin, @NotNull PyType[] elementTypes) { + this(origin.getPyClass(), elementTypes, false); } public String getName() { - return "(" + StringUtil.join(myElementTypes, new Function() { - @Nullable - public String fun(PyType pyType) { - return pyType == null ? PyNames.UNKNOWN_TYPE : pyType.getName(); - } - }, ", ") + ")"; + if (myHomogeneous) { + return "(" + (getTypeName(myElementTypes[0])) + ", ...)"; + } + return "(" + StringUtil.join(myElementTypes, PyTupleType::getTypeName, ", ") + ")"; + } + + @Nullable + private static String getTypeName(@Nullable PyType type) { + return type == null ? PyNames.UNKNOWN_TYPE : type.getName(); } @Override @@ -76,6 +88,9 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType } public PyType getElementType(int index) { + if (myHomogeneous) { + return myElementTypes[0]; + } if (index >= 0 && index < myElementTypes.length) { return myElementTypes[index]; } @@ -83,7 +98,11 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType } public int getElementCount() { - return myElementTypes.length; + return myHomogeneous ? -1 : myElementTypes.length; + } + + public boolean isHomogeneous() { + return myHomogeneous; } @Override diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 0fca0bf00649..0b5dc60a6fe1 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -532,6 +532,25 @@ public class PyTypingTest extends PyTestCase { "expr = f"); } + // PY-18762 + public void testHomogeneousTuple() { + doTest("Tuple[int, ...]", + "from typing import Tuple\n" + + "\n" + + "def f(xs: Tuple[int, ...]):\n" + + " expr = xs"); + } + + // PY-18762 + public void testFunctionTypeCommentHomogeneousTuple() { + doTest("Tuple[int, ...]", + "from typing import Tuple\n" + + "\n" + + "def f(xs):\n" + + " # type: (Tuple[int, ...]) -> None\n" + + " expr = xs\n"); + } + // PY-18741 public void testFunctionTypeCommentWithParamTypeComment() { doTest("(x: int, y: bool, z: Any) -> str",