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.
This commit is contained in:
Mikhail Golubev
2017-03-06 15:20:44 +03:00
parent 41aaebef69
commit 46c229030e
10 changed files with 131 additions and 26 deletions
@@ -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<PyClassLikeType> {
@Nullable
String getClassQName();
@@ -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<T extends PyInstantiableType<T>> extends PyType {
boolean isDefinition();
T toInstance();
}
@@ -66,6 +66,7 @@ public interface PyType {
Key<Set<String>> 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
@@ -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);
}
}
}
@@ -199,6 +199,21 @@ public class PyTypeModelBuilder {
visitor.classObject(this);
}
}
static class GenericType extends TypeModel {
private final String name;
private final List<TypeModel> bounds;
public GenericType(@Nullable String name, @NotNull List<TypeModel> 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<TypeModel> 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<TypeModel> parameterModels = null;
final List<PyCallableParameter> 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(")");
}
}
}
@@ -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<PyGenericType> {
@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;
}
}
+1
View File
@@ -0,0 +1 @@
<html><body><code>def <b>f</b>(p1:&nbsp;T1,&nbsp;p2:&nbsp;T2,&nbsp;p3:&nbsp;T3)<br>Inferred&nbsp;type:&nbsp;(p1:&nbsp;TypeVar('T1',&nbsp;<a href="psi_element://#typename#int">int</a>),&nbsp;p2:&nbsp;TypeVar('T2',&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#str">str</a>),&nbsp;p3:&nbsp;TypeVar('T3',&nbsp;List[<a href="psi_element://#typename#bool">bool</a>]))&nbsp;-&gt;&nbsp;None<br></code></body></html>
+10
View File
@@ -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
<the_ref>f()
@@ -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);
}
}
@@ -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" +