diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 187a366578c6..5e93ad07fd1d 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -219,7 +219,7 @@ class PyDocumentationBuilder { } } else if (followed != null && outer instanceof PyReferenceExpression) { - myBody.addItem("\nInferred type: "); + myBody.addItem(combUp("\nInferred type: ")); PythonDocumentationProvider.describeExpressionTypeWithLinks(myBody, (PyReferenceExpression)outer, TypeEvalContext.slow()); } if (myBody.isEmpty() && myEpilog.isEmpty()) { diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java new file mode 100644 index 000000000000..47bb64e73f35 --- /dev/null +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -0,0 +1,314 @@ +package com.jetbrains.python.documentation; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyNamedParameter; +import com.jetbrains.python.psi.PyParameter; +import com.jetbrains.python.psi.types.*; +import com.jetbrains.python.toolbox.ChainIterable; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; + +import static com.jetbrains.python.documentation.DocumentationBuilderKit.$; +import static com.jetbrains.python.documentation.DocumentationBuilderKit.combUp; + +/** + * @author traff + */ +public class PyTypeModelBuilder { + @NonNls static final String UNKNOWN = "unknown"; + private final Map myVisited = Maps.newHashMap(); + private final TypeEvalContext myContext; + + PyTypeModelBuilder(TypeEvalContext context) { + this.myContext = context; + } + + abstract static class TypeModel { + abstract void accept(TypeVisitor visitor); + + public String asString() { + TypeToStringVisitor visitor = new TypeToStringVisitor(); + this.accept(visitor); + return visitor.getString(); + } + + public void toBodyWithLinks(@NotNull ChainIterable body, @NotNull PsiElement anchor) { + TypeToBodyWithLinksVisitor visitor = new TypeToBodyWithLinksVisitor(body, anchor); + this.accept(visitor); + } + } + + static class OneOf extends TypeModel { + private Collection oneOfTypes; + + private OneOf(Collection oneOfTypes) { + this.oneOfTypes = oneOfTypes; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.oneOf(this); + } + } + + static class CollectionOf extends TypeModel { + private String collectionName; + private TypeModel elementType; + + private CollectionOf(String collectionName, TypeModel elementType) { + this.collectionName = collectionName; + this.elementType = elementType; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.collectionOf(this); + } + } + + static class NamedType extends TypeModel { + private String name; + + private NamedType(String name) { + this.name = name; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.name(this.name); + } + } + + private static TypeModel _(String name) { + return new NamedType(name); + } + + static class FunctionType extends TypeModel { + private TypeModel returnType; + private Collection parameters; + + FunctionType(@NotNull TypeModel returnType, Collection parameters) { + this.returnType = returnType; + this.parameters = parameters; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.function(this); + } + } + + static class ParamType extends TypeModel { + private final String name; + private final TypeModel type; + + + private ParamType(String name, @Nullable TypeModel type) { + this.name = name; + this.type = type; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.param(this); + } + } + + /** + * Builds tree-like type model for PyType + * + * @param type + * @param allowUnions + * @return + */ + public TypeModel build(@Nullable PyType type, + boolean allowUnions) { + final TypeModel evaluated = myVisited.get(type); + if (evaluated != null) { + return evaluated; + } + if (myVisited.containsKey(type)) { //already evaluating? + return type != null ? _(type.getName()) : _(UNKNOWN); + } + myVisited.put(type, null); //mark as evaluating + + TypeModel result = null; + if (type instanceof PyTypeReference) { + final PyType resolved = ((PyTypeReference)type).resolve(null, myContext); + if (resolved != null) { + result = build(resolved, true); + } + } + else if (type instanceof PyCollectionType) { + final String name = type.getName(); + final PyType elementType = ((PyCollectionType)type).getElementType(myContext); + if (elementType != null) { + result = new CollectionOf(name, build(elementType, true)); + } + } + else if (type instanceof PyUnionType && allowUnions) { + if (type instanceof PyDynamicallyEvaluatedType) { + result = build(((PyDynamicallyEvaluatedType)type).exclude(null, myContext), true); + } + else { + result = new OneOf( + Collections2.transform(((PyUnionType)type).getMembers(), new Function() { + @Override + public TypeModel apply(PyType t) { + return build(t, false); + } + })); + } + } + if (result == null) { + result = type != null ? _(type.getName()) : _(UNKNOWN); + } + myVisited.put(type, result); + return result; + } + + + public TypeModel build(PyFunction function) { + final PyType returnType = function.getReturnType(myContext, null); + return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(function.getParameterList().getParameters()), + new Function() { + @Override + public TypeModel apply(PyParameter p) { + final PyNamedParameter np = p.getAsNamed(); + if (np != null) { + TypeModel paramType = + _(UNKNOWN); + final PyType t = np.getType(myContext); + if (t != null) { + paramType = build(t, true); + } + return new ParamType(np.getName(), paramType); + } + return new ParamType(p.toString(), null); + } + })); + } + + private interface TypeVisitor { + void oneOf(OneOf oneOf); + + void collectionOf(CollectionOf collectionOf); + + void name(String name); + + void function(FunctionType type); + + void param(ParamType text); + } + + private static class TypeToStringVisitor extends TypeNameVisitor { + private final StringBuilder myStringBuilder = new StringBuilder(); + + @Override + protected void add(String s) { + myStringBuilder.append(s); + } + + @Override + protected void addType(String name) { + add(name); + } + + public String getString() { + return myStringBuilder.toString(); + } + } + + private static class TypeToBodyWithLinksVisitor extends TypeNameVisitor { + private ChainIterable myBody; + private PsiElement myAnchor; + + public TypeToBodyWithLinksVisitor(ChainIterable body, PsiElement anchor) { + myBody = body; + myAnchor = anchor; + } + + @Override + protected void add(String s) { + myBody.addItem(combUp(s)); + } + + @Override + protected void addType(String name) { + PyType type = PyTypeParser.getTypeByName(myAnchor, name); + if (type instanceof PyClassType) { + myBody.addWith(new DocumentationBuilderKit.LinkWrapper(PythonDocumentationProvider.LINK_TYPE_TYPENAME + name), + $(name)); + } + else { + add(name); + } + } + } + + private abstract static class TypeNameVisitor implements TypeVisitor { + @Override + public void oneOf(OneOf oneOf) { + add("one of ("); + processListCommaSeparated(oneOf.oneOfTypes); + add(")"); + } + + private void processListCommaSeparated(Collection list) { + boolean first = true; + for (TypeModel t : list) { + if (!first) { + add(", "); + } + else { + first = false; + } + + t.accept(this); + } + } + + protected abstract void add(String s); + + @Override + public void collectionOf(CollectionOf collectionOf) { + addType(collectionOf.collectionName); + add(" of "); + collectionOf.elementType.accept(this); + } + + protected abstract void addType(String name); + + @Override + public void name(String name) { + addType(name); + } + + @Override + public void function(FunctionType function) { + add("("); + processListCommaSeparated(function.parameters); + add(") -> "); + function.returnType.accept(this); + add("\n"); + } + + @Override + public void param(ParamType param) { + add(param.name); + if (param.type != null) { + add(": "); + param.type.accept(this); + } + } + } +} diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 5536c68fa067..92f3c9319120 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -1,7 +1,5 @@ package com.jetbrains.python.documentation; -import com.google.common.collect.Collections2; -import com.google.common.collect.Maps; import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.lang.documentation.AbstractDocumentationProvider; import com.intellij.lang.documentation.ExternalDocumentationProvider; @@ -19,7 +17,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Function; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.console.PydevConsoleRunner; @@ -57,8 +54,6 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i @NonNls private static final String RST_PREFIX = ":"; @NonNls private static final String EPYDOC_PREFIX = "@"; - @NonNls private static final String UNKNOWN = "unknown"; - // provides ctrl+hover info public String getQuickNavigateInfo(final PsiElement element, PsiElement originalElement) { if (element instanceof PyFunction) { @@ -125,7 +120,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i cat.addItem(escaper.apply(PyUtil.getReadableRepr(fun.getParameterList(), false))); if (!PyNames.INIT.equals(name)) { cat.addItem(escaper.apply("\nInferred type: ")); - cat.addItem(escaper.apply(getTypeDescription(fun))); + getTypeDescription(fun, cat); } return cat; } @@ -156,30 +151,24 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i public static String getTypeDescription(@NotNull PyFunction fun) { final TypeEvalContext context = TypeEvalContext.slow(); - final PyType returnType = fun.getReturnType(context, null); - return String.format("(%s) -> %s\n", - StringUtil.join(fun.getParameterList().getParameters(), - new Function() { - @Override - public String fun(PyParameter p) { - final PyNamedParameter np = p.getAsNamed(); - if (np != null) { - String name = UNKNOWN; - final PyType t = np.getType(context); - if (t != null) { - name = getTypeName(t, context); - } - return String.format("%s: %s", np.getName(), name); - } - return p.toString(); - } - }, ", "), - returnType != null ? getTypeName(returnType, context) : UNKNOWN); + PyTypeModelBuilder builder = new PyTypeModelBuilder(context); + return builder.build(fun).asString(); + } + + public static void getTypeDescription(@NotNull PyFunction fun, ChainIterable body) { + final TypeEvalContext context = TypeEvalContext.slow(); + PyTypeModelBuilder builder = new PyTypeModelBuilder(context); + builder.build(fun).toBodyWithLinks(body, fun); } public static String getTypeName(@Nullable PyType type, @NotNull final TypeEvalContext context) { - TypeNameBuilder builder = new TypeNameBuilder(context); - return builder.build(type, true).asString(); + PyTypeModelBuilder.TypeModel typeModel = buildTypeModel(type, context); + return typeModel.asString(); + } + + private static PyTypeModelBuilder.TypeModel buildTypeModel(PyType type, TypeEvalContext context) { + PyTypeModelBuilder builder = new PyTypeModelBuilder(context); + return builder.build(type, true); } public static void describeExpressionTypeWithLinks(ChainIterable body, @@ -192,212 +181,10 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i public static void describeTypeWithLinks(ChainIterable body, PsiElement anchor, PyType type, TypeEvalContext context) { - TypeNameBuilder builder = new TypeNameBuilder(context); + PyTypeModelBuilder builder = new PyTypeModelBuilder(context); builder.build(type, true).toBodyWithLinks(body, anchor); } - private static class TypeNameBuilder { - private final Map myVisited = Maps.newHashMap(); - private final TypeEvalContext myContext; - - private TypeNameBuilder(TypeEvalContext context) { - this.myContext = context; - } - - private abstract static class Type { - abstract void accept(TypeVisitor visitor); - - public String asString() { - TypeToStringVisitor visitor = new TypeToStringVisitor(); - this.accept(visitor); - return visitor.getString(); - } - - public void toBodyWithLinks(@NotNull ChainIterable body, @NotNull PsiElement anchor) { - TypeToBodyWithLinksVisitor visitor = new TypeToBodyWithLinksVisitor(body, anchor); - this.accept(visitor); - } - } - - private static class OneOf extends Type { - private Collection oneOfTypes; - - private OneOf(Collection oneOfTypes) { - this.oneOfTypes = oneOfTypes; - } - - @Override - void accept(TypeVisitor visitor) { - visitor.oneOf(this); - } - } - - private static class CollectionOf extends Type { - private String collectionName; - private Type elementType; - - private CollectionOf(String collectionName, Type elementType) { - this.collectionName = collectionName; - this.elementType = elementType; - } - - @Override - void accept(TypeVisitor visitor) { - visitor.collectionOf(this); - } - } - - private static class NamedType extends Type { - private String name; - - private NamedType(String name) { - this.name = name; - } - - @Override - void accept(TypeVisitor visitor) { - visitor.name(this.name); - } - } - - private static Type _(String name) { - return new NamedType(name); - } - - private Type build(@Nullable PyType type, - boolean allowUnions) { - final Type evaluated = myVisited.get(type); - if (evaluated != null) { - return evaluated; - } - if (myVisited.containsKey(type)) { //already evaluating? - return type != null ? _(type.getName()) : _(UNKNOWN); - } - myVisited.put(type, null); //mark as evaluating - - Type result = null; - if (type instanceof PyTypeReference) { - final PyType resolved = ((PyTypeReference)type).resolve(null, myContext); - if (resolved != null) { - result = build(resolved, true); - } - } - else if (type instanceof PyCollectionType) { - final String name = type.getName(); - final PyType elementType = ((PyCollectionType)type).getElementType(myContext); - if (elementType != null) { - result = new CollectionOf(name, build(elementType, true)); - } - } - else if (type instanceof PyUnionType && allowUnions) { - if (type instanceof PyDynamicallyEvaluatedType) { - result = build(((PyDynamicallyEvaluatedType)type).exclude(null, myContext), true); - } - else { - result = new OneOf(Collections2.transform(((PyUnionType)type).getMembers(), new com.google.common.base.Function() { - @Override - public Type apply(PyType t) { - return build(t, false); - } - })); - } - } - if (result == null) { - result = type != null ? _(type.getName()) : _(UNKNOWN); - } - myVisited.put(type, result); - return result; - } - - private interface TypeVisitor { - void oneOf(OneOf oneOf); - - void collectionOf(CollectionOf collectionOf); - - void name(String name); - } - - private static class TypeToStringVisitor extends TypeNameVisitor { - private final StringBuilder myStringBuilder = new StringBuilder(); - - @Override - protected void add(String s) { - myStringBuilder.append(s); - } - - @Override - protected void addType(String name) { - add(name); - } - - public String getString() { - return myStringBuilder.toString(); - } - } - - private static class TypeToBodyWithLinksVisitor extends TypeNameVisitor { - private ChainIterable myBody; - private PsiElement myAnchor; - - public TypeToBodyWithLinksVisitor(ChainIterable body, PsiElement anchor) { - myBody = body; - myAnchor = anchor; - } - - @Override - protected void add(String s) { - myBody.addItem(combUp(s)); - } - - @Override - protected void addType(String name) { - PyType type = PyTypeParser.getTypeByName(myAnchor, name); - if (type instanceof PyClassType) { - myBody.addWith(new LinkWrapper(LINK_TYPE_TYPENAME + name), - $(name)); - } - else { - add(name); - } - } - } - - private abstract static class TypeNameVisitor implements TypeVisitor { - @Override - public void oneOf(OneOf oneOf) { - add("one of ("); - boolean first = true; - for (Type t : oneOf.oneOfTypes) { - if (!first) { - add(", "); - } - else { - first = false; - } - - t.accept(this); - } - add(")"); - } - - protected abstract void add(String s); - - @Override - public void collectionOf(CollectionOf collectionOf) { - addType(collectionOf.collectionName); - add(" of "); - collectionOf.elementType.accept(this); - } - - protected abstract void addType(String name); - - @Override - public void name(String name) { - addType(name); - } - } - } - static ChainIterable describeDecorators(PyDecoratable what, FP.Lambda1, Iterable> deco_name_wrapper, String deco_separator, FP.Lambda1 escaper) { diff --git a/python/testData/quickdoc/HoverOverFunction.html b/python/testData/quickdoc/HoverOverFunction.html index 2cc640737b52..bc8a92ce6a0e 100644 --- a/python/testData/quickdoc/HoverOverFunction.html +++ b/python/testData/quickdoc/HoverOverFunction.html @@ -1,2 +1,2 @@ def foo(arg) -Inferred type: (arg: unknown) -> int \ No newline at end of file +Inferred type: (arg: unknown) -> int
\ No newline at end of file diff --git a/python/testData/quickdoc/HoverOverMethod.html b/python/testData/quickdoc/HoverOverMethod.html index d5573d5c07c5..3e83346b1f45 100644 --- a/python/testData/quickdoc/HoverOverMethod.html +++ b/python/testData/quickdoc/HoverOverMethod.html @@ -1,3 +1,3 @@ class A def f(self) -Inferred type: (self: A) -> int \ No newline at end of file +Inferred type: (self: A) -> int
\ No newline at end of file diff --git a/python/testData/quickdoc/InheritedMethod.html b/python/testData/quickdoc/InheritedMethod.html index fdc542967a2c..dcd7be2306a3 100644 --- a/python/testData/quickdoc/InheritedMethod.html +++ b/python/testData/quickdoc/InheritedMethod.html @@ -1 +1 @@ -class B(A)

def foo(self)
Inferred type: (self: B) -> None


Documentation is missing. The following is copied from A.foo.

Doc from A.foo. \ No newline at end of file +class B(A)

def foo(self)
Inferred type: (self: B) -> None


Documentation is missing. The following is copied from A.foo.

Doc from A.foo. \ No newline at end of file diff --git a/python/testData/quickdoc/Method.html b/python/testData/quickdoc/Method.html index 9058c638bf86..c99d4b3cbd1b 100644 --- a/python/testData/quickdoc/Method.html +++ b/python/testData/quickdoc/Method.html @@ -1 +1 @@ -class Foo

@deco
def meth(self)
Inferred type: (self: Foo) -> None


Doc of meth.
+class Foo

@deco
def meth(self)
Inferred type: (self: Foo) -> None


Doc of meth.
\ No newline at end of file diff --git a/python/testData/quickdoc/PropNewDeleter.html b/python/testData/quickdoc/PropNewDeleter.html index 7eb1694db918..2e18d12d552f 100644 --- a/python/testData/quickdoc/PropNewDeleter.html +++ b/python/testData/quickdoc/PropNewDeleter.html @@ -1 +1 @@ -property x of class A(object)
Copied from getter:
Does things to X

@x.deleter
def x(self, v)
Inferred type: (self: A, v: unknown) -> None

Deletes X


Deleter of property

\ No newline at end of file +property x of class A(object)
Copied from getter:
Does things to X

@x.deleter
def x(self, v)
Inferred type: (self: A, v: unknown) -> None

Deletes X


Deleter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/PropNewGetter.html b/python/testData/quickdoc/PropNewGetter.html index 023595817797..a34472bfd899 100644 --- a/python/testData/quickdoc/PropNewGetter.html +++ b/python/testData/quickdoc/PropNewGetter.html @@ -1 +1 @@ -property x of class A(object)

@property
def x(self)
Inferred type: (self: A) -> int

Does things to X


Getter of property

\ No newline at end of file +property x of class A(object)

@property
def x(self)
Inferred type: (self: A) -> int

Does things to X


Getter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/PropNewSetter.html b/python/testData/quickdoc/PropNewSetter.html index a0d21fffc151..b04baf8a3e49 100644 --- a/python/testData/quickdoc/PropNewSetter.html +++ b/python/testData/quickdoc/PropNewSetter.html @@ -1 +1 @@ -property x of class A(object)
Copied from getter:
Does things to X

@x.setter
def x(self, v)
Inferred type: (self: A, v: unknown) -> None

Sets X


Setter of property

\ No newline at end of file +property x of class A(object)
Copied from getter:
Does things to X

@x.setter
def x(self, v)
Inferred type: (self: A, v: unknown) -> None

Sets X


Setter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/PropOldDeleter.html b/python/testData/quickdoc/PropOldDeleter.html index 7bee8c05c9c4..b0187dbe49c6 100644 --- a/python/testData/quickdoc/PropOldDeleter.html +++ b/python/testData/quickdoc/PropOldDeleter.html @@ -1 +1 @@ -property x of class A(object)

def __getX(self)
Inferred type: (self: A) -> unknown

Doc of getter


Deleter of property

\ No newline at end of file +property x of class A(object)

def __getX(self)
Inferred type: (self: A) -> unknown

Doc of getter


Deleter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/PropOldGetter.html b/python/testData/quickdoc/PropOldGetter.html index 33869d12057c..40d2a92c7a7c 100644 --- a/python/testData/quickdoc/PropOldGetter.html +++ b/python/testData/quickdoc/PropOldGetter.html @@ -1 +1 @@ -property x of class A(object)

def __getX(self)
Inferred type: (self: A) -> unknown

Doc of getter


Getter of property

\ No newline at end of file +property x of class A(object)

def __getX(self)
Inferred type: (self: A) -> unknown

Doc of getter


Getter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/PropOldSetter.html b/python/testData/quickdoc/PropOldSetter.html index d407da52745d..5fbbb3f96a28 100644 --- a/python/testData/quickdoc/PropOldSetter.html +++ b/python/testData/quickdoc/PropOldSetter.html @@ -1 +1 @@ -property x of class A(object)

def __getX(self, x)
Inferred type: (self: A, x: unknown) -> None

Doc of getter


Setter of property

\ No newline at end of file +property x of class A(object)

def __getX(self, x)
Inferred type: (self: A, x: unknown) -> None

Doc of getter


Setter of property

\ No newline at end of file diff --git a/python/testData/quickdoc/Variable.html b/python/testData/quickdoc/Variable.html index f6eee509a9e4..62d6e80474b1 100644 --- a/python/testData/quickdoc/Variable.html +++ b/python/testData/quickdoc/Variable.html @@ -1 +1 @@ -Assigned to y

Inferred type: one of (int, str)
\ No newline at end of file +Assigned to y

Inferred type: one of (int, str)
\ No newline at end of file