mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
Andrey Vlasovskikh
parent
158a4453ce
commit
3c8bc79731
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user