From a68e6981dc302f969dda83239ea39e4676e4fce3 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 15 Mar 2016 21:04:59 +0300 Subject: [PATCH] PY-18816 Type comments for target expressions are stored inside their stubs --- .../python/psi/PyTargetExpression.java | 2 +- .../psi/stubs/PyTargetExpressionStub.java | 3 ++ .../codeInsight/PyTypingTypeProvider.java | 36 +------------ .../python/psi/PyFileElementType.java | 2 +- .../psi/impl/PyTargetExpressionImpl.java | 50 +++++++++++++++++++ .../stubs/PyTargetExpressionElementType.java | 12 +++-- .../stubs/PyTargetExpressionStubImpl.java | 17 ++++++- .../stubs/TargetExpressionTypeComment.py | 1 + .../com/jetbrains/python/PyStubsTest.java | 17 ++++++- 9 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 python/testData/stubs/TargetExpressionTypeComment.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java index c7ee950742c8..9012137bb53f 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java @@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable; */ public interface PyTargetExpression extends PyQualifiedExpression, PsiNamedElement, PsiNameIdentifierOwner, PyDocStringOwner, PyQualifiedNameOwner, PyReferenceOwner, StubBasedPsiElement, - PyPossibleClassMember { + PyPossibleClassMember, PyTypeCommentOwner { PyTargetExpression[] EMPTY_ARRAY = new PyTargetExpression[0]; /** diff --git a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyTargetExpressionStub.java b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyTargetExpressionStub.java index 26f6301380cb..4f7574069f6c 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyTargetExpressionStub.java +++ b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyTargetExpressionStub.java @@ -63,4 +63,7 @@ public interface PyTargetExpressionStub extends NamedStub { @Nullable String getDocString(); + + @Nullable + String getTypeComment(); } diff --git a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java index e8db6982fcf7..a9fb38accb78 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java @@ -21,7 +21,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; -import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.util.PsiTreeUtil; @@ -173,9 +172,9 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { @Override public PyType getReferenceType(@NotNull PsiElement referenceTarget, TypeEvalContext context, @Nullable PsiElement anchor) { - if (referenceTarget instanceof PyTargetExpression && context.maySwitchToAST(referenceTarget)) { + if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; - final String comment = getTypeComment(target); + final String comment = target.getTypeCommentAnnotation(); if (comment != null) { final PyType type = getStringBasedType(comment, referenceTarget, new Context(context)); if (type instanceof PyTupleType) { @@ -190,20 +189,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return null; } - @Nullable - private static String getTypeComment(@NotNull PyTargetExpression target) { - final PsiElement commentContainer = PsiTreeUtil.getParentOfType(target, PyAssignmentStatement.class, PyWithStatement.class, - PyForPart.class); - if (commentContainer != null) { - final PsiComment comment = getSameLineTrailingCommentChild(commentContainer); - if (comment != null) { - final String text = comment.getText(); - return getTypeCommentValue(text); - } - } - return null; - } - /** * Checks that text of a comment starts with the "type:" prefix and returns trimmed part afterwards. This trailing part is supposed to * contain type annotation in PEP 484 compatible format, that can be parsed with either {@link PyTypeParser#parse(PsiElement, String)} @@ -218,23 +203,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return null; } - @Nullable - private static PsiComment getSameLineTrailingCommentChild(@NotNull PsiElement element) { - PsiElement child = element.getFirstChild(); - while (true) { - if (child == null) { - return null; - } - if (child instanceof PsiComment) { - return (PsiComment)child; - } - if (child.getText().contains("\n")) { - return null; - } - child = child.getNextSibling(); - } - } - private static boolean isAny(@NotNull PyType type) { return type instanceof PyClassType && "typing.Any".equals(((PyClassType)type).getPyClass().getQualifiedName()); } diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index b6c737ab0464..fb9f1b52eb68 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -62,7 +62,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 54; + return 55; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index a25a0ca8ec15..5961557c01c6 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; @@ -35,6 +36,7 @@ import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonDialectsTokenSetProvider; +import com.jetbrains.python.codeInsight.PyTypingTypeProvider; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; @@ -677,4 +679,52 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl imp private final InitializerType myInitializerType; private final QualifiedName myInitializer; private final boolean myQualified; - @Nullable private final String myDocString; + private final String myTypeComment; + @Nullable private final String myDocString; private final CustomTargetExpressionStub myCustomStub; public PyTargetExpressionStubImpl(String name, @Nullable String docString, + @Nullable String typeComment, CustomTargetExpressionStub customStub, StubElement parent) { super(parent, PyElementTypes.TARGET_EXPRESSION); myName = name; + myTypeComment = typeComment; myInitializerType = InitializerType.Custom; myInitializer = null; myQualified = false; @@ -48,12 +51,16 @@ public class PyTargetExpressionStubImpl extends StubBase imp myDocString = docString; } - public PyTargetExpressionStubImpl(final String name, @Nullable String docString, final InitializerType initializerType, + public PyTargetExpressionStubImpl(final String name, + @Nullable String docString, + final InitializerType initializerType, final QualifiedName initializer, final boolean qualified, + @Nullable String typeComment, final StubElement parentStub) { super(parentStub, PyElementTypes.TARGET_EXPRESSION); myName = name; + myTypeComment = typeComment; assert initializerType != InitializerType.Custom; myInitializerType = initializerType; myInitializer = initializer; @@ -94,6 +101,12 @@ public class PyTargetExpressionStubImpl extends StubBase imp return myDocString; } + @Nullable + @Override + public String getTypeComment() { + return myTypeComment; + } + @Override public String toString() { return "PyTargetExpressionStub(name=" + myName + ")"; diff --git a/python/testData/stubs/TargetExpressionTypeComment.py b/python/testData/stubs/TargetExpressionTypeComment.py new file mode 100644 index 000000000000..b192e5590cff --- /dev/null +++ b/python/testData/stubs/TargetExpressionTypeComment.py @@ -0,0 +1 @@ +x = unknown() # type: int \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index c02f98b22703..fdc83b56871f 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -445,9 +445,24 @@ public class PyStubsTest extends PyTestCase { assertEquals("int", annotation); assertNotParsed(file); - final TypeEvalContext context = TypeEvalContext.codeInsightFallback(myFixture.getProject()); + final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file); final PyType paramType = context.getType(param); assertInstanceOf(paramType, PyClassType.class); assertNotParsed(file); } + + public void testTargetExpressionTypeComment() { + final PyFile file = getTestFile(); + final PyTargetExpression target = file.findTopLevelAttribute("x"); + assertNotNull(target); + + final String annotation = target.getTypeCommentAnnotation(); + assertEquals("int", annotation); + assertNotParsed(file); + + final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file); + final PyType paramType = context.getType(target); + assertInstanceOf(paramType, PyClassType.class); + assertNotParsed(file); + } }