From 46c229030ea39a41f150c218193e8e21e94ca5f4 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 14 Feb 2017 14:37:31 +0300 Subject: [PATCH] PY-20057 Pull up isDefinition() to PyGenericType via new PyInstantiableType interface Additionally, I've changed rendering of type vars so that it's done in PyTypeModelBuilder rather than in PyGenericType itself in order to properly wrap type vars in "Type[]" before displaying them. Moreover, it's fixed a couple of cases uncovered earlier: formatting a type var constrained with a single upper bound and formatting a type var constrained with some compound type, e.g. generic. --- .../python/psi/types/PyClassLikeType.java | 5 +- .../python/psi/types/PyInstantiableType.java | 27 +++++++++ .../jetbrains/python/psi/types/PyType.java | 1 + .../typing/PyTypingTypeProvider.java | 9 ++- .../documentation/PyTypeModelBuilder.java | 59 +++++++++++++++++-- .../python/psi/types/PyGenericType.java | 38 +++++++----- python/testData/quickdoc/TypeVars.html | 1 + python/testData/quickdoc/TypeVars.py | 10 ++++ .../com/jetbrains/python/PyQuickDocTest.java | 5 ++ .../com/jetbrains/python/PyTypingTest.java | 2 +- 10 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 python/psi-api/src/com/jetbrains/python/psi/types/PyInstantiableType.java create mode 100644 python/testData/quickdoc/TypeVars.html create mode 100644 python/testData/quickdoc/TypeVars.py 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" +