diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java index f7d40633fd48..f962f3cd6548 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassLikeType.java @@ -31,10 +31,7 @@ import java.util.Set; /** * @author vlan */ -public interface PyClassLikeType extends PyCallableType, PyWithAncestors { - boolean isDefinition(); - - PyClassLikeType toInstance(); +public interface PyClassLikeType extends PyCallableType, PyWithAncestors, PyInstantiableType { @Nullable String getClassQName(); diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java new file mode 100644 index 000000000000..18ea6331140d --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.psi.types; + +/** + * A type that can describe either particular instantiated instance of a class or a class itself. + * + * @author Mikhail Golubev + */ +public interface PyInstantiableType> extends PyType { + boolean isDefinition(); + + T toInstance(); +} diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java index a458f9488951..4a668e23759c 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyType.java @@ -66,6 +66,7 @@ public interface PyType { Key> CTX_NAMES = new Key<>("Completion variants names"); /** + * TODO rename it to something like getPresentableName(), because it's not clear that these names are actually visible to end-user * @return name of the type */ @Nullable diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index d1de08405a58..d392e60a0064 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -516,8 +516,13 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { 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); + final PyClassType classType = as(type, PyClassType.class); + if (classType != null && !classType.isDefinition()) { + return new PyClassTypeImpl(classType.getPyClass(), true); + } + final PyGenericType typeVar = as(type, PyGenericType.class); + if (typeVar != null && !typeVar.isDefinition()) { + return new PyGenericType(typeVar.getName(), typeVar.getBound(), true); } } } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 2f39404ecfa8..b243d1423ff8 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -199,6 +199,21 @@ public class PyTypeModelBuilder { visitor.classObject(this); } } + + static class GenericType extends TypeModel { + private final String name; + private final List bounds; + + public GenericType(@Nullable String name, @NotNull List bounds) { + this.name = name; + this.bounds = bounds; + } + + @Override + void accept(@NotNull TypeVisitor visitor) { + visitor.genericType(this); + } + } /** * Builds tree-like type model for PyType @@ -262,10 +277,25 @@ public class PyTypeModelBuilder { } } else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { - result = build((PyCallableType)type); + result = buildCallable((PyCallableType)type); } - else if (type instanceof PyClassType && !((PyClassType)type).isDefinition()) { - result = new ClassObjectType(build(type, allowUnions)); + else if (type instanceof PyInstantiableType && ((PyInstantiableType)type).isDefinition()) { + result = new ClassObjectType(build(((PyInstantiableType)type).toInstance(), allowUnions)); + } + else if (type instanceof PyGenericType) { + //assert !((PyGenericType)type).isDefinition() + final PyType bound = ((PyGenericType)type).getBound(); + final List boundNames; + if (bound instanceof PyUnionType) { + boundNames = ContainerUtil.map(((PyUnionType)bound).getMembers(), t -> build(t, allowUnions)); + } + else if (bound != null) { + boundNames = Collections.singletonList(build(bound, allowUnions)); + } + else { + boundNames = Collections.emptyList(); + } + result = new GenericType(type.getName(), boundNames); } if (result == null) { result = NamedType.nameOrAny(type); @@ -295,7 +325,7 @@ public class PyTypeModelBuilder { return null; } - private TypeModel build(@NotNull PyCallableType type) { + private TypeModel buildCallable(@NotNull PyCallableType type) { List parameterModels = null; final List parameters = type.getParameters(myContext); if (parameters != null) { @@ -327,6 +357,8 @@ public class PyTypeModelBuilder { void tuple(TupleType type); void classObject(ClassObjectType type); + + void genericType(GenericType type); } private static class TypeToStringVisitor extends TypeNameVisitor { @@ -528,5 +560,24 @@ public class PyTypeModelBuilder { type.classType.accept(this); add("]"); } + + @Override + public void genericType(GenericType type) { + add("TypeVar('"); + add(type.name); + add("'"); + if (!type.bounds.isEmpty()) { + add(", "); + boolean first = true; + for (TypeModel bound : type.bounds) { + if (!first) { + add(", "); + } + bound.accept(this); + first = false; + } + } + add(")"); + } } } diff --git a/python/src/com/jetbrains/python/psi/types/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index b3f47fb9a4ae..9d419bc33f93 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -15,11 +15,9 @@ */ 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.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; -import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.AccessDirection; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.resolve.PyResolveContext; @@ -28,17 +26,24 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Objects; /** * @author vlan */ -public class PyGenericType implements PyType { +public class PyGenericType implements PyType, PyInstantiableType { @NotNull private final String myName; - @Nullable private PyType myBound; + @Nullable private final PyType myBound; + private boolean myIsDefinition = false; public PyGenericType(@NotNull String name, @Nullable PyType bound) { + this(name, bound, false); + } + + public PyGenericType(@NotNull String name, @Nullable PyType bound, boolean isDefinition) { myName = name; myBound = bound; + myIsDefinition = isDefinition; } @Nullable @@ -52,20 +57,13 @@ public class PyGenericType implements PyType { @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - return new Object[0]; + return ArrayUtil.EMPTY_OBJECT_ARRAY; } @NotNull @Override public String getName() { - if (myBound instanceof PyUnionType) { - final PyUnionType bounds = (PyUnionType)myBound; - final String boundsString = StringUtil.join(bounds.getMembers(), type -> type != null ? type.getName() : PyNames.UNKNOWN_TYPE, ", "); - return "TypeVar('" + myName + "', " + boundsString + ")"; - } - else { - return "TypeVar('" + myName + "')"; - } + return myName; } @Override @@ -86,7 +84,7 @@ public class PyGenericType implements PyType { return false; } final PyGenericType type = (PyGenericType)o; - return myName.equals(type.myName); + return myName.equals(type.myName) && Objects.equals(myBound, type.getBound()) && myIsDefinition == type.isDefinition(); } @Override @@ -104,4 +102,14 @@ public class PyGenericType implements PyType { public PyType getBound() { return myBound; } + + @Override + public boolean isDefinition() { + return myIsDefinition; + } + + @Override + public PyGenericType toInstance() { + return myIsDefinition ? new PyGenericType(myName, myBound, false) : this; + } } diff --git a/python/testData/quickdoc/TypeVars.html b/python/testData/quickdoc/TypeVars.html new file mode 100644 index 000000000000..344bc1b3b4a7 --- /dev/null +++ b/python/testData/quickdoc/TypeVars.html @@ -0,0 +1 @@ +def f(p1: T1, p2: T2, p3: T3)
Inferred type: (p1: TypeVar('T1', int), p2: TypeVar('T2', int, str), p3: TypeVar('T3', List[bool])) -> None
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVars.py b/python/testData/quickdoc/TypeVars.py new file mode 100644 index 000000000000..5bbb833557a2 --- /dev/null +++ b/python/testData/quickdoc/TypeVars.py @@ -0,0 +1,10 @@ +from typing import TypeVar, List + +T1 = TypeVar('T1', int) +T2 = TypeVar('T2', int, str) +T3 = TypeVar('T3', List[bool]) + +def f(p1: T1, p2: T2, p3: T3): + pass + +f() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 7746b33e004b..d635a492df46 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -277,4 +277,9 @@ public class PyQuickDocTest extends LightMarkedTestCase { public void testUnknownTuple() { runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); } + + public void testTypeVars() { + myFixture.copyDirectoryToProject("typing", ""); + runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); + } } diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 4b64e1e9e884..21e08c7948c4 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -797,7 +797,7 @@ public class PyTypingTest extends PyTestCase { // PY-20057 public void testConstrainedClassObjectTypeOfParam() { - doTest("Type[T]", + doTest("Type[TypeVar('T', int)]", "from typing import Type, TypeVar\n" + "\n" + "T = TypeVar('T', bound=int)\n" +