PY-20057 Unify type vars constrained by class types and instance types

so that annotations like "def f(x: Type[T]) -> T" (and vice-versa) work
as expected.

Additionally, I've added a new method toClass() in PyInstantiableType
interface that is supposed to be the opposite to existing toInstance().
This commit is contained in:
Mikhail Golubev
2017-03-06 15:20:44 +03:00
parent 46c229030e
commit b6dbc5b989
9 changed files with 82 additions and 4 deletions
@@ -128,11 +128,18 @@ public class PyJavaClassType implements PyClassLikeType {
return myDefinition;
}
@NotNull
@Override
public PyClassLikeType toInstance() {
return myDefinition ? new PyJavaClassType(myClass, false) : this;
}
@NotNull
@Override
public PyClassLikeType toClass() {
return myDefinition ? this : new PyJavaClassType(myClass, true);
}
@Nullable
@Override
public String getClassQName() {
@@ -15,6 +15,8 @@
*/
package com.jetbrains.python.psi.types;
import org.jetbrains.annotations.NotNull;
/**
* A type that can describe either particular instantiated instance of a class or a class itself.
*
@@ -23,5 +25,9 @@ package com.jetbrains.python.psi.types;
public interface PyInstantiableType<T extends PyInstantiableType<T>> extends PyType {
boolean isDefinition();
@NotNull
T toInstance();
@NotNull
T toClass();
}
@@ -84,14 +84,23 @@ public class PyCustomType implements PyClassLikeType {
return !myInstanceType;
}
@NotNull
@Override
public final PyClassLikeType toInstance() {
return myInstanceType
? this
: new PyCustomType(myQualifiedName, myFilter, true, myTypesToMimic.toArray(new PyClassLikeType[myTypesToMimic.size()]));
: new PyCustomType(myQualifiedName, myFilter, true, myTypesToMimic.toArray(new PyClassLikeType[0]));
}
@NotNull
@Override
public PyClassLikeType toClass() {
return myInstanceType
? new PyCustomType(myQualifiedName, myFilter, false, myTypesToMimic.toArray(new PyClassLikeType[0]))
: this;
}
@Nullable
@Override
public final String getClassQName() {
@@ -103,11 +103,18 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
return null;
}
@NotNull
@Override
public PyClassType toInstance() {
return myDefinitionLevel == 1 ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 0) : this;
}
@NotNull
@Override
public PyClassLikeType toClass() {
return myDefinitionLevel == 0 ? this : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 1);
}
@Override
public String toString() {
return "PyNamedTupleType: " + myName;
@@ -107,11 +107,18 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return myIsDefinition;
}
@NotNull
@Override
public PyClassType toInstance() {
return myIsDefinition ? withUserDataCopy(new PyClassTypeImpl(myClass, false)) : this;
}
@NotNull
@Override
public PyClassLikeType toClass() {
return myIsDefinition ? this : new PyClassTypeImpl(myClass, true);
}
/**
* Wrap new instance to copy user data to it
*/
@@ -70,11 +70,18 @@ public class PyCollectionTypeImpl extends PyClassTypeImpl implements PyCollectio
return new PyCollectionTypeImpl(pyClass, isDefinition, elementTypes);
}
@NotNull
@Override
public PyClassType toInstance() {
return myIsDefinition ? withUserDataCopy(new PyCollectionTypeImpl(myClass, false, myElementTypes)) : this;
}
@NotNull
@Override
public PyClassLikeType toClass() {
return myIsDefinition ? this : withUserDataCopy(new PyCollectionTypeImpl(myClass, true, myElementTypes));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -108,8 +108,15 @@ public class PyGenericType implements PyType, PyInstantiableType<PyGenericType>
return myIsDefinition;
}
@NotNull
@Override
public PyGenericType toInstance() {
return myIsDefinition ? new PyGenericType(myName, myBound, false) : this;
}
@NotNull
@Override
public PyGenericType toClass() {
return myIsDefinition ? this : new PyGenericType(myName, myBound, true);
}
}
@@ -416,7 +416,22 @@ public class PyTypeChecker {
@NotNull TypeEvalContext context) {
if (hasGenerics(type, context)) {
if (type instanceof PyGenericType) {
final PyType substitution = substitutions.get((PyGenericType)type);
final PyGenericType typeVar = (PyGenericType)type;
PyType substitution = substitutions.get(typeVar);
if (substitution == null) {
if (!typeVar.isDefinition()) {
final PyInstantiableType<?> classType = as(substitutions.get(typeVar.toClass()), PyInstantiableType.class);
if (classType != null) {
substitution = classType.toInstance();
}
}
else {
final PyInstantiableType<?> instanceType = as(substitutions.get(typeVar.toInstance()), PyInstantiableType.class);
if (instanceType != null) {
substitution = instanceType.toClass();
}
}
}
if (substitution instanceof PyGenericType && substitution != type) {
final PyType recursive = substitute(substitution, substitutions, context);
if (recursive != null) {
@@ -807,11 +807,11 @@ public class PyTypingTest extends PyTestCase {
}
// PY-20057
public void testConstrainedClassObjectTypeOfResultValue() {
public void testFunctionCreatesInstanceFromType() {
doTest("int",
"from typing import Type, TypeVar\n" +
"\n" +
"T = TypeVar('T', bound=int)\n" +
"T = TypeVar('T')\n" +
"\n" +
"def f(x: Type[T]) -> T:\n" +
" return x()\n" +
@@ -819,6 +819,19 @@ public class PyTypingTest extends PyTestCase {
"expr = f(int)");
}
// PY-20057
public void testFunctionReturnsTypeOfInstance() {
doTest("Type[int]",
"from typing import Type, TypeVar\n" +
"\n" +
"T = TypeVar('T')\n" +
"\n" +
"def f(x: T) -> Type[T]:\n" +
" return type(T)\n" +
" \n" +
"expr = f(42)");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());