PY-19826 Fixed: Generic type of list not inferred when created from tuple

Inherit PyTupleType from PyCollectionType so that getElementTypes().get(i) returns type of 'i'-th element in the tuple
This commit is contained in:
fitermay
2016-10-13 21:28:18 +03:00
committed by Semyon Proshev
parent 8349f69f4b
commit aab0d3e87a
15 changed files with 191 additions and 82 deletions
@@ -18,6 +18,7 @@ package com.jetbrains.python.documentation;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.PyTypingTypeProvider;
import com.jetbrains.python.psi.types.*;
@@ -197,7 +198,17 @@ public class PyTypeModelBuilder {
myVisited.put(type, null); //mark as evaluating
TypeModel result = null;
if (type instanceof PyCollectionType) {
if (type instanceof PyTupleType) {
final PyTupleType tupleType = (PyTupleType)type;
final List<PyType> elementTypes = tupleType.isHomogeneous()
? Collections.singletonList(tupleType.getElementType(0))
: tupleType.getElementTypes(myContext);
final List<TypeModel> elementModels = ContainerUtil.map(elementTypes, elementType -> build(elementType, true));
result = new TupleType(elementModels, tupleType.isHomogeneous());
}
else if (type instanceof PyCollectionType) {
final String name = type.getName();
final List<PyType> elementTypes = ((PyCollectionType)type).getElementTypes(myContext);
boolean nullOnlyTypes = true;
@@ -232,15 +243,6 @@ public class PyTypeModelBuilder {
else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) {
result = build((PyCallableType)type);
}
else if (type instanceof PyTupleType) {
final List<TypeModel> elementModels = new ArrayList<>();
final PyTupleType tupleType = (PyTupleType)type;
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, tupleType.isHomogeneous());
}
if (result == null) {
result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE);
}
@@ -364,7 +366,7 @@ public class PyTypeModelBuilder {
@Override
public void oneOf(OneOf oneOf) {
myDepth++;
if (myDepth>MAX_DEPTH) {
if (myDepth > MAX_DEPTH) {
add("...");
return;
}
@@ -393,7 +395,7 @@ public class PyTypeModelBuilder {
@Override
public void collectionOf(CollectionOf collectionOf) {
myDepth++;
if (myDepth>MAX_DEPTH) {
if (myDepth > MAX_DEPTH) {
add("...");
return;
}
@@ -416,7 +418,7 @@ public class PyTypeModelBuilder {
@Override
public void function(FunctionType function) {
myDepth++;
if (myDepth>MAX_DEPTH) {
if (myDepth > MAX_DEPTH) {
add("...");
return;
}
@@ -436,7 +438,7 @@ public class PyTypeModelBuilder {
@Override
public void param(ParamType param) {
myDepth++;
if (myDepth>MAX_DEPTH) {
if (myDepth > MAX_DEPTH) {
add("...");
return;
}
@@ -205,7 +205,7 @@ public class PyCallExpressionHelper {
}
}
final List<PyExpression> resolvedQualifiers = resolveResult != null ? resolveResult.getQualifiers() : null;
final List<PyExpression> qualifiers = resolvedQualifiers != null ? resolvedQualifiers : Collections.<PyExpression>emptyList();
final List<PyExpression> qualifiers = resolvedQualifiers != null ? resolvedQualifiers : Collections.emptyList();
final TypeEvalContext context = resolveContext.getTypeEvalContext();
if (resolved instanceof PyFunction) {
final PyFunction function = (PyFunction)resolved;
@@ -267,7 +267,7 @@ public class PyCallExpressionHelper {
QualifiedResolveResult followed = callReference.followAssignmentsChain(resolveContext);
final List<PyExpression> qualifiers = followed.getQualifiers();
final PyExpression firstQualifier = qualifiers != null && !qualifiers.isEmpty() ? qualifiers.get(0) : null;
boolean isByInstance = isQualifiedByInstance(function, qualifiers != null ? qualifiers : Collections.<PyExpression>emptyList(),
boolean isByInstance = isQualifiedByInstance(function, qualifiers != null ? qualifiers : Collections.emptyList(),
resolveContext.getTypeEvalContext());
final boolean isConstructorCall = isConstructorName(function.getName()) &&
(!callReference.isQualified() || !isConstructorName(callReference.getName()));
@@ -539,14 +539,21 @@ public class PyCallExpressionHelper {
}
if (init != null) {
final PyType t = init.getCallType(context, call);
if (cls != null) {
if (init.getContainingClass() != cls) {
if (t instanceof PyCollectionType) {
final List<PyType> elementTypes = ((PyCollectionType)t).getElementTypes(context);
return Ref.create(new PyCollectionTypeImpl(cls, false, elementTypes));
}
return Ref.create(new PyClassTypeImpl(cls, false));
if (cls != null && cls != init.getContainingClass()) {
if (t instanceof PyTupleType) {
final PyTupleType tupleType = (PyTupleType)t;
final List<PyType> elementTypes = tupleType.getElementTypes(context);
final PyTupleType newTupleType = new PyTupleType(cls, elementTypes.toArray(new PyType[0]), tupleType.isHomogeneous());
return Ref.create(newTupleType);
}
if (t instanceof PyCollectionType) {
final List<PyType> elementTypes = ((PyCollectionType)t).getElementTypes(context);
return Ref.create(new PyCollectionTypeImpl(cls, false, elementTypes));
}
return Ref.create(new PyClassTypeImpl(cls, false));
}
if (t != null && !(t instanceof PyNoneType)) {
return Ref.create(t);
@@ -680,10 +687,10 @@ public class PyCallExpressionHelper {
final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset);
if (markedCallee == null || argumentList == null) {
return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.<PyExpression, PyNamedParameter>emptyMap(),
Collections.<PyParameter>emptyList(), Collections.<PyExpression>emptyList(),
Collections.<PyNamedParameter>emptyList(), Collections.<PyNamedParameter>emptyList(),
Collections.<PyExpression, PyTupleParameter>emptyMap());
return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.emptyMap(),
Collections.emptyList(), Collections.emptyList(),
Collections.emptyList(), Collections.emptyList(),
Collections.emptyMap());
}
final TypeEvalContext context = resolveContext.getTypeEvalContext();
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
@@ -124,21 +124,21 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
public Icon getIcon(int flags) {
PyPsiUtils.assertValid(this);
final Property property = getProperty();
if (property != null) {
if (property.getGetter().valueOrNull() == this) {
return PythonIcons.Python.PropertyGetter;
}
if (property.getSetter().valueOrNull() == this) {
return PythonIcons.Python.PropertySetter;
}
if (property.getDeleter().valueOrNull() == this) {
return PythonIcons.Python.PropertyDeleter;
}
return PlatformIcons.PROPERTY_ICON;
if (property != null) {
if (property.getGetter().valueOrNull() == this) {
return PythonIcons.Python.PropertyGetter;
}
if (getContainingClass() != null) {
return PlatformIcons.METHOD_ICON;
if (property.getSetter().valueOrNull() == this) {
return PythonIcons.Python.PropertySetter;
}
if (property.getDeleter().valueOrNull() == this) {
return PythonIcons.Python.PropertyDeleter;
}
return PlatformIcons.PROPERTY_ICON;
}
if (getContainingClass() != null) {
return PlatformIcons.METHOD_ICON;
}
return PythonIcons.Python.Function;
}
@@ -324,14 +324,18 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
public void visitPyYieldExpression(PyYieldExpression node) {
final PyExpression expr = node.getExpression();
final PyType type = expr != null ? context.getType(expr) : null;
if (node.isDelegating()) {
if (type instanceof PyCollectionType) {
if (type instanceof PyTupleType) {
types.addAll(((PyTupleType)type).getElementTypes(context));
}
else if (type instanceof PyCollectionType) {
final PyCollectionType collectionType = (PyCollectionType)type;
// TODO: Select the parameter types that matches T in Iterable[T]
final List<PyType> elementTypes = collectionType.getElementTypes(context);
types.add(elementTypes.isEmpty() ? null : elementTypes.get(0));
}
else if (ArrayUtil.contains(type, cache.getListType(), cache.getDictType(), cache.getSetType())) {
else if (ArrayUtil.contains(type, cache.getListType(), cache.getDictType(), cache.getSetType(), cache.getTupleType())) {
types.add(null);
}
else {
@@ -605,7 +609,7 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
if (inlineComment != null && PyTypingTypeProvider.getTypeCommentValue(inlineComment.getText()) != null) {
return inlineComment;
}
final PyStatementList statements = getStatementList();
if (statements.getStatements().length != 0) {
final PsiComment comment = as(statements.getFirstChild(), PsiComment.class);
@@ -621,7 +625,7 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
public String getTypeCommentAnnotation() {
final PyFunctionStub stub = getStub();
if (stub != null) {
return stub.getTypeComment();
return stub.getTypeComment();
}
final PsiComment comment = getTypeComment();
if (comment != null) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,20 +77,13 @@ public class PyCollectionTypeImpl extends PyClassTypeImpl implements PyCollectio
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PyCollectionType)) return false;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PyCollectionType type = (PyCollectionType)o;
final PyCollectionTypeImpl that = (PyCollectionTypeImpl)o;
if (!myElementTypes.equals(that.myElementTypes)) return false;
final TypeEvalContext context = TypeEvalContext.codeInsightFallback(myClass.getProject());
final List<PyType> otherElementTypes = type.getElementTypes(context);
if (myElementTypes.size() != otherElementTypes.size()) return false;
for (int i = 0; i < myElementTypes.size(); i++) {
final PyType elementType = myElementTypes.get(i);
final PyType otherElementType = otherElementTypes.get(i);
if (elementType == null && otherElementType != null) return false;
if (elementType != null && !elementType.equals(otherElementType)) return false;
}
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,11 +26,12 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
/**
* @author yole
*/
public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType {
public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType, PyCollectionType {
private final PyType[] myElementTypes;
private final boolean myHomogeneous;
@@ -47,12 +48,12 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType
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 new PyTupleType(tuple, new PyType[]{elementType}, true);
}
return null;
}
PyTupleType(@NotNull PyClass tupleClass, @NotNull PyType[] elementTypes, boolean homogeneous) {
public PyTupleType(@NotNull PyClass tupleClass, @NotNull PyType[] elementTypes, boolean homogeneous) {
super(tupleClass, false);
myElementTypes = elementTypes;
myHomogeneous = homogeneous;
@@ -124,4 +125,10 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType
result = 31 * result + (myElementTypes != null ? Arrays.hashCode(myElementTypes) : 0);
return result;
}
@NotNull
@Override
public List<PyType> getElementTypes(@NotNull TypeEvalContext context) {
return Arrays.asList(myElementTypes);
}
}
@@ -18,6 +18,7 @@ package com.jetbrains.python.psi.types;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
@@ -116,22 +117,7 @@ public class PyTypeChecker {
if (expected instanceof PyClassType && actual instanceof PyClassType) {
final PyClass superClass = ((PyClassType)expected).getPyClass();
final PyClass subClass = ((PyClassType)actual).getPyClass();
if (expected instanceof PyCollectionType && actual instanceof PyCollectionType) {
if (!matchClasses(superClass, subClass, context)) {
return false;
}
// TODO: Match generic parameters based on the correspondence between the generic parameters of subClass and its base classes
final List<PyType> superElementTypes = ((PyCollectionType)expected).getElementTypes(context);
final List<PyType> subElementTypes = ((PyCollectionType)actual).getElementTypes(context);
for (int i = 0; i < subElementTypes.size(); i++) {
final PyType superElementType = i < superElementTypes.size() ? superElementTypes.get(i) : null;
if (!match(superElementType, subElementTypes.get(i), context, substitutions, recursive)) {
return false;
}
}
return true;
}
else if (expected instanceof PyTupleType && actual instanceof PyTupleType) {
if (expected instanceof PyTupleType && actual instanceof PyTupleType) {
final PyTupleType superTupleType = (PyTupleType)expected;
final PyTupleType subTupleType = (PyTupleType)actual;
if (!superTupleType.isHomogeneous() && !subTupleType.isHomogeneous()) {
@@ -163,6 +149,39 @@ public class PyTypeChecker {
return match(superTupleType.getElementType(0), subTupleType.getElementType(0), context);
}
}
else if (expected instanceof PyCollectionType && actual instanceof PyTupleType) {
if (!matchClasses(superClass, subClass, context)) {
return false;
}
final PyTupleType actualTupleType = (PyTupleType)actual;
final PyType superElementType = ContainerUtil.getFirstItem(((PyCollectionType)expected).getElementTypes(context));
final PyType subElementType = actualTupleType.isHomogeneous()
? actualTupleType.getElementType(0)
: PyUnionType.union(actualTupleType.getElementTypes(context));
if (!match(superElementType, subElementType, context, substitutions, recursive)) {
return false;
}
return true;
}
else if (expected instanceof PyCollectionType && actual instanceof PyCollectionType) {
if (!matchClasses(superClass, subClass, context)) {
return false;
}
// TODO: Match generic parameters based on the correspondence between the generic parameters of subClass and its base classes
final List<PyType> superElementTypes = ((PyCollectionType)expected).getElementTypes(context);
final List<PyType> subElementTypes = ((PyCollectionType)actual).getElementTypes(context);
for (int i = 0; i < subElementTypes.size(); i++) {
final PyType superElementType = i < superElementTypes.size() ? superElementTypes.get(i) : null;
if (!match(superElementType, subElementTypes.get(i), context, substitutions, recursive)) {
return false;
}
}
return true;
}
else if (matchClasses(superClass, subClass, context)) {
return true;
}
@@ -295,12 +314,6 @@ public class PyTypeChecker {
collectGenerics(t, context, collected, visited);
}
}
else if (type instanceof PyCollectionType) {
final PyCollectionType collection = (PyCollectionType)type;
for (PyType elementType : collection.getElementTypes(context)) {
collectGenerics(elementType, context, collected, visited);
}
}
else if (type instanceof PyTupleType) {
final PyTupleType tuple = (PyTupleType)type;
final int n = tuple.isHomogeneous() ? 1 : tuple.getElementCount();
@@ -308,6 +321,12 @@ public class PyTypeChecker {
collectGenerics(tuple.getElementType(i), context, collected, visited);
}
}
else if (type instanceof PyCollectionType) {
final PyCollectionType collection = (PyCollectionType)type;
for (PyType elementType : collection.getElementTypes(context)) {
collectGenerics(elementType, context, collected, visited);
}
}
else if (type instanceof PyCallableType) {
final PyCallableType callable = (PyCallableType)type;
final List<PyCallableParameter> parameters = callable.getParameters(context);
@@ -0,0 +1 @@
<html><body><code>def <b>get_tuple</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;Tuple[<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#str">str</a>]<br></code></body></html>
@@ -0,0 +1,4 @@
def get_tuple() -> tuple[int, int, str]:
pass
<the_ref>get_tuple()
@@ -0,0 +1 @@
<html><body><code>def <b>get_tuple</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;Tuple[<a href="psi_element://#typename#str">str</a>,&nbsp;...]<br></code></body></html>
@@ -0,0 +1,4 @@
def get_tuple() -> tuple[str, ...]:
pass
<the_ref>get_tuple()
@@ -0,0 +1 @@
<html><body><code>def <b>get_tuple</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#tuple">tuple</a><br></code></body></html>
+4
View File
@@ -0,0 +1,4 @@
def get_tuple() -> tuple:
pass
<the_ref>get_tuple()
@@ -84,6 +84,40 @@ public class Py3TypeTest extends PyTestCase {
" pass"));
}
public void testYieldFromHomogeneousTuple() {
myFixture.copyDirectoryToProject("typing", "");
doTest("str",
"import typing\n"+
"def get_tuple() -> typing.Tuple[str, ...]:\n" +
" pass\n" +
"def gen()\n" +
" yield from get_tuple()\n" +
"for expr in gen():" +
" pass");
}
public void testYieldFromHeterogeneousTuple() {
myFixture.copyDirectoryToProject("typing", "");
doTest("Union[int, str]",
"import typing\n" +
"def get_tuple() -> typing.Tuple[int, int, str]:\n" +
" pass\n" +
"def gen()\n" +
" yield from get_tuple()\n" +
"for expr in gen():" +
" pass");
}
public void testYieldFromUnknownTuple() {
doTest("Any",
"def get_tuple() -> tuple:\n" +
" pass\n" +
"def gen()\n" +
" yield from get_tuple()\n" +
"for expr in gen():" +
" pass");
}
public void testYieldFromUnknownList() {
doTest("Any",
"def get_list() -> list:\n" +
@@ -265,4 +265,16 @@ public class PyQuickDocTest extends LightMarkedTestCase {
public void testOptionalParameterType() {
checkHTMLOnly();
}
public void testHomogeneousTuple() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
public void testHeterogeneousTuple() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
public void testUnknownTuple() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
}
@@ -886,6 +886,22 @@ public class PyTypeTest extends PyTestCase {
" expr, foo = xs\n");
}
// PY-19826
public void testListFromTuple() {
doTest("List[Union[str, int]]",
"expr = list(('1', 2, 3))");
}
public void testDictFromTuple() {
doTest("Dict[Union[str, int], Union[str, int]]",
"expr = dict((('1', 1), (2, 2), (3, '3')))");
}
public void testSetFromTuple() {
doTest("Set[Union[str, int]]",
"expr = set(('1', 2, 3))");
}
public void testHomogeneousTupleSubstitution() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,