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.
This commit is contained in:
Mikhail Golubev
2016-03-17 18:40:27 +03:00
committed by Andrey Vlasovskikh
parent 158a4453ce
commit 3c8bc79731
4 changed files with 81 additions and 23 deletions
@@ -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<PyType> 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) {
@@ -129,9 +129,11 @@ public class PyTypeModelBuilder {
static class TupleType extends TypeModel {
private final List<TypeModel> members;
private final boolean homogeneous;
public TupleType(List<TypeModel> members) {
public TupleType(List<TypeModel> members, boolean homogeneous) {
this.members = members;
this.homogeneous = homogeneous;
}
@Override
@@ -244,11 +246,11 @@ public class PyTypeModelBuilder {
else if (type instanceof PyTupleType) {
final List<TypeModel> elementModels = new ArrayList<TypeModel>();
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("]");
}
}
@@ -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<PyType, String>() {
@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
@@ -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",