mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><code>def <b>f</b>(p1: T1, p2: T2, p3: T3)<br>Inferred type: (p1: TypeVar('T1', <a href="psi_element://#typename#int">int</a>), p2: TypeVar('T2', <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#str">str</a>), p3: TypeVar('T3', List[<a href="psi_element://#typename#bool">bool</a>])) -> None<br></code></body></html>
|
||||
@@ -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" +
|
||||
|
||||
Reference in New Issue
Block a user