PY-20057 Initial support of typing.Type (types of direct classes)

This commit is contained in:
Mikhail Golubev
2017-03-06 15:20:44 +03:00
parent 46fd99f987
commit 41aaebef69
3 changed files with 85 additions and 0 deletions
@@ -62,6 +62,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
public static final String COROUTINE = "typing.Coroutine";
public static final String NAMEDTUPLE = "typing.NamedTuple";
public static final String GENERIC = "typing.Generic";
public static final String TYPE = "typing.Type";
public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)");
@@ -482,6 +483,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
if (genericType != null) {
return Ref.create(genericType);
}
final PyType classObjType = getClassObjectType(resolved, context);
if (classObjType != null) {
return Ref.create(classObjType);
}
final Ref<PyType> classType = getClassType(resolved, context.getTypeContext());
if (classType != null) {
return classType;
@@ -501,6 +506,25 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
}
}
@Nullable
private static PyType getClassObjectType(@Nullable PsiElement resolved, @NotNull Context context) {
if (resolved instanceof PySubscriptionExpression) {
final PySubscriptionExpression subsExpr = (PySubscriptionExpression)resolved;
final PyExpression operand = subsExpr.getOperand();
final Collection<String> operandNames = resolveToQualifiedNames(operand, context.getTypeContext());
if (operandNames.contains(TYPE)) {
final PyExpression indexExpr = subsExpr.getIndexExpression();
if (indexExpr != null) {
final PyType type = Ref.deref(getType(indexExpr, context));
if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) {
return new PyClassTypeImpl(((PyClassType)type).getPyClass(), false);
}
}
}
}
return null;
}
@Nullable
private static Ref<PyType> getAnyType(@NotNull PsiElement element) {
final PyQualifiedNameOwner qualifiedNameOwner = as(element, PyQualifiedNameOwner.class);
@@ -186,6 +186,19 @@ public class PyTypeModelBuilder {
visitor.param(this);
}
}
static class ClassObjectType extends TypeModel {
private final TypeModel classType;
public ClassObjectType(TypeModel classType) {
this.classType = classType;
}
@Override
void accept(@NotNull TypeVisitor visitor) {
visitor.classObject(this);
}
}
/**
* Builds tree-like type model for PyType
@@ -251,6 +264,9 @@ public class PyTypeModelBuilder {
else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) {
result = build((PyCallableType)type);
}
else if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) {
result = new ClassObjectType(build(type, allowUnions));
}
if (result == null) {
result = NamedType.nameOrAny(type);
}
@@ -309,6 +325,8 @@ public class PyTypeModelBuilder {
void optional(OptionalType type);
void tuple(TupleType type);
void classObject(ClassObjectType type);
}
private static class TypeToStringVisitor extends TypeNameVisitor {
@@ -503,5 +521,12 @@ public class PyTypeModelBuilder {
}
add("]");
}
@Override
public void classObject(ClassObjectType type) {
add("Type[");
type.classType.accept(this);
add("]");
}
}
}
@@ -783,6 +783,42 @@ public class PyTypingTest extends PyTestCase {
"expr = C(0).get()\n");
}
// PY-20057
public void testClassObjectType() {
doTest("Type[MyClass]",
"from typing import Type\n" +
"\n" +
"class MyClass:\n" +
" pass\n" +
"\n" +
"def f(x: Type[MyClass]): \n" +
" expr = x");
}
// PY-20057
public void testConstrainedClassObjectTypeOfParam() {
doTest("Type[T]",
"from typing import Type, TypeVar\n" +
"\n" +
"T = TypeVar('T', bound=int)\n" +
"\n" +
"def f(x: Type[T]):\n" +
" expr = x");
}
// PY-20057
public void testConstrainedClassObjectTypeOfResultValue() {
doTest("int",
"from typing import Type, TypeVar\n" +
"\n" +
"T = TypeVar('T', bound=int)\n" +
"\n" +
"def f(x: Type[T]) -> T:\n" +
" return x()\n" +
"\n" +
"expr = f(int)");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());